Skip to content

Commit a95e88e

Browse files
committed
WIP
1 parent dfcc78d commit a95e88e

13 files changed

+358
-38
lines changed

src/Commands/ConfigureIdleQuery.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace DirectoryTree\ImapEngine\Laravel\Commands;
4+
5+
use DirectoryTree\ImapEngine\MessageQueryInterface;
6+
7+
class ConfigureIdleQuery
8+
{
9+
/**
10+
* Constructor.
11+
*/
12+
public function __construct(
13+
protected array $with = []
14+
) {}
15+
16+
/**
17+
* Configure the query.
18+
*/
19+
public function __invoke(MessageQueryInterface $query): MessageQueryInterface
20+
{
21+
if (in_array('flags', $this->with)) {
22+
$query->withFlags();
23+
}
24+
25+
if (in_array('body', $this->with)) {
26+
$query->withBody();
27+
}
28+
29+
if (in_array('headers', $this->with)) {
30+
$query->withHeaders();
31+
}
32+
33+
return $query;
34+
}
35+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace DirectoryTree\ImapEngine\Laravel\Commands;
4+
5+
use DirectoryTree\ImapEngine\Laravel\Events\MessageReceived;
6+
use DirectoryTree\ImapEngine\MessageInterface;
7+
use Illuminate\Support\Facades\Event;
8+
9+
class HandleMessageReceived
10+
{
11+
/**
12+
* Constructor.
13+
*/
14+
public function __construct(
15+
protected WatchMailbox $command
16+
) {}
17+
18+
/**
19+
* Handle the message received event.
20+
*/
21+
public function __invoke(MessageInterface $message): void
22+
{
23+
$this->command->info(
24+
"Message received: [{$message->uid()}]"
25+
);
26+
27+
Event::dispatch(new MessageReceived($message));
28+
}
29+
}

src/Commands/WatchMailbox.php

Lines changed: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
namespace DirectoryTree\ImapEngine\Laravel\Commands;
44

5-
use DirectoryTree\ImapEngine\Laravel\Events\MessageReceived;
5+
use DirectoryTree\ImapEngine\FolderInterface;
66
use DirectoryTree\ImapEngine\Laravel\Facades\Imap;
7+
use DirectoryTree\ImapEngine\Laravel\Support\LoopInterface;
8+
use DirectoryTree\ImapEngine\MailboxInterface;
79
use DirectoryTree\ImapEngine\Message;
8-
use DirectoryTree\ImapEngine\MessageQuery;
910
use Exception;
1011
use Illuminate\Console\Command;
11-
use Illuminate\Support\Facades\Event;
1212
use Illuminate\Support\Str;
1313

1414
class WatchMailbox extends Command
@@ -18,7 +18,7 @@ class WatchMailbox extends Command
1818
*
1919
* @var string
2020
*/
21-
protected $signature = 'imap:watch {mailbox} {folder?} {--with=} {--timeout=30} {--debug=false}';
21+
protected $signature = 'imap:watch {mailbox} {folder?} {--with=} {--timeout=30} {--attempts=5} {--debug=false}';
2222

2323
/**
2424
* The console command description.
@@ -30,63 +30,57 @@ class WatchMailbox extends Command
3030
/**
3131
* Execute the console command.
3232
*/
33-
public function handle(): void
33+
public function handle(LoopInterface $loop): void
3434
{
3535
$mailbox = Imap::mailbox($name = $this->argument('mailbox'));
3636

3737
$with = explode(',', $this->option('with'));
3838

39-
$this->info("Watching mailbox $name...");
39+
$this->info("Watching mailbox [$name]...");
4040

4141
$attempts = 0;
4242

43-
while (true) {
43+
$loop->run(function () use ($mailbox, $with, &$attempts) {
4444
try {
45-
$inbox = ($folder = $this->argument('folder'))
46-
? $mailbox->folders()->findOrFail($folder)
47-
: $mailbox->inbox();
45+
$folder = $this->folder($mailbox);
4846

4947
$attempts = 0;
5048

51-
$inbox->idle(function (Message $message) {
52-
$this->info("Message received: [{$message->uid()}]");
53-
54-
Event::dispatch(new MessageReceived($message));
55-
}, function (MessageQuery $query) use ($with) {
56-
if (in_array('flags', $with)) {
57-
$query->withFlags();
58-
}
59-
60-
if (in_array('body', $with)) {
61-
$query->withBody();
62-
}
63-
64-
if (in_array('headers', $with)) {
65-
$query->withHeaders();
66-
}
67-
68-
return $query;
69-
}, $this->option('timeout'));
49+
$folder->idle(
50+
new HandleMessageReceived($this),
51+
new ConfigureIdleQuery($with),
52+
$this->option('timeout')
53+
);
7054
} catch (Exception $e) {
7155
if ($this->isMessageMissing($e)) {
72-
continue;
56+
return;
7357
}
7458

7559
if ($this->isDisconnection($e)) {
7660
sleep(2);
7761

78-
continue;
62+
return;
7963
}
8064

81-
if ($attempts >= 5) {
65+
if ($attempts >= $this->option('attempts')) {
8266
$this->info("Exception: {$e->getMessage()}");
8367

8468
throw $e;
8569
}
8670

8771
$attempts++;
8872
}
89-
}
73+
});
74+
}
75+
76+
/**
77+
* Get the mailbox folder to idle.
78+
*/
79+
protected function folder(MailboxInterface $mailbox): FolderInterface
80+
{
81+
return ($folder = $this->argument('folder'))
82+
? $mailbox->folders()->findOrFail($folder)
83+
: $mailbox->inbox();
9084
}
9185

9286
/**

src/Events/MessageReceived.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22

33
namespace DirectoryTree\ImapEngine\Laravel\Events;
44

5-
use DirectoryTree\ImapEngine\Message;
5+
use DirectoryTree\ImapEngine\MessageInterface;
66

77
class MessageReceived
88
{
99
/**
1010
* Create a new event instance.
1111
*/
1212
public function __construct(
13-
public Message $message
13+
public MessageInterface $message
1414
) {}
1515
}

src/ImapManager.php

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,31 @@ public function mailbox(string $name): MailboxInterface
3535
return $this->mailboxes[$name];
3636
}
3737

38-
if (! array_key_exists($name, $this->config['mailboxes'])) {
38+
if (! array_key_exists($name, $this->config['mailboxes'] ?? [])) {
3939
throw new InvalidArgumentException(
4040
"Mailbox [{$name}] is not defined. Please check your IMAP configuration."
4141
);
4242
}
4343

44-
return $this->mailboxes[$name] = new Mailbox($this->config['mailboxes'][$name]);
44+
return $this->mailboxes[$name] = $this->build($this->config['mailboxes'][$name]);
45+
}
46+
47+
/**
48+
* Register a mailbox instance.
49+
*/
50+
public function register(string $name, array $config): static
51+
{
52+
$this->mailboxes[$name] = $this->build($config);
53+
54+
return $this;
55+
}
56+
57+
/**
58+
* Build an on-demand mailbox instance.
59+
*/
60+
public function build(array $config): MailboxInterface
61+
{
62+
return new Mailbox($config);
4563
}
4664

4765
/**

src/ImapServiceProvider.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace DirectoryTree\ImapEngine\Laravel;
44

5+
use DirectoryTree\ImapEngine\Laravel\Support\Loop;
6+
use DirectoryTree\ImapEngine\Laravel\Support\LoopInterface;
57
use Illuminate\Support\ServiceProvider;
68

79
class ImapServiceProvider extends ServiceProvider
@@ -12,8 +14,10 @@ class ImapServiceProvider extends ServiceProvider
1214
public function register(): void
1315
{
1416
$this->app->singleton(ImapManager::class, function () {
15-
return new ImapManager(config('imap'));
17+
return new ImapManager(config('imap', []));
1618
});
19+
20+
$this->app->bind(LoopInterface::class, Loop::class);
1721
}
1822

1923
/**

src/Support/Loop.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace DirectoryTree\ImapEngine\Laravel\Support;
4+
5+
class Loop implements LoopInterface
6+
{
7+
/**
8+
* {@inheritDoc}
9+
*/
10+
public function run(callable $tick): void
11+
{
12+
while (true) {
13+
$tick();
14+
}
15+
}
16+
}

src/Support/LoopFake.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace DirectoryTree\ImapEngine\Laravel\Support;
4+
5+
class LoopFake implements LoopInterface
6+
{
7+
/**
8+
* {@inheritDoc}
9+
*/
10+
public function run(callable $tick): void
11+
{
12+
$tick();
13+
}
14+
}

src/Support/LoopInterface.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
namespace DirectoryTree\ImapEngine\Laravel\Support;
4+
5+
interface LoopInterface
6+
{
7+
/**
8+
* Execute the loop.
9+
*/
10+
public function run(callable $tick): void;
11+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
use DirectoryTree\ImapEngine\Laravel\Commands\ConfigureIdleQuery;
4+
use DirectoryTree\ImapEngine\MessageQueryInterface;
5+
use DirectoryTree\ImapEngine\Testing\FakeFolder;
6+
7+
test('it does nothing when "with" is empty', function () {
8+
$folder = new FakeFolder;
9+
10+
$configure = new ConfigureIdleQuery;
11+
12+
$query = $configure($folder->messages());
13+
14+
expect($query)->toBeInstanceOf(MessageQueryInterface::class);
15+
});
16+
17+
test('it configures query when "with" is present', function () {
18+
$folder = new FakeFolder;
19+
20+
$configure = new ConfigureIdleQuery([
21+
'flags', 'body', 'headers',
22+
]);
23+
24+
$query = $configure($folder->messages());
25+
26+
expect($query->isFetchingBody())->toBeTrue();
27+
expect($query->isFetchingFlags())->toBeTrue();
28+
expect($query->isFetchingHeaders())->toBeTrue();
29+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
use DirectoryTree\ImapEngine\Laravel\Commands\HandleMessageReceived;
4+
use DirectoryTree\ImapEngine\Laravel\Commands\WatchMailbox;
5+
use DirectoryTree\ImapEngine\Testing\FakeMessage;
6+
7+
test('it dispatches event', function () {
8+
$command = mock(WatchMailbox::class);
9+
10+
$command->shouldReceive('info')->once()->with(
11+
'Message received: [123]'
12+
);
13+
14+
$handle = new HandleMessageReceived($command);
15+
16+
$handle(new FakeMessage(123));
17+
});

tests/Commands/WatchMailboxTest.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace DirectoryTree\ImapEngine\Laravel\Tests;
4+
5+
use DirectoryTree\ImapEngine\Laravel\Commands\WatchMailbox;
6+
use DirectoryTree\ImapEngine\Laravel\Facades\Imap;
7+
use DirectoryTree\ImapEngine\Laravel\Support\LoopFake;
8+
use DirectoryTree\ImapEngine\Laravel\Support\LoopInterface;
9+
use DirectoryTree\ImapEngine\Testing\FakeFolder;
10+
use Illuminate\Support\Facades\App;
11+
use Illuminate\Support\Facades\Config;
12+
use InvalidArgumentException;
13+
14+
use function Pest\Laravel\artisan;
15+
16+
test('it throws exception when mailbox is not configured', function () {
17+
artisan(WatchMailbox::class, ['mailbox' => 'invalid']);
18+
})->throws(
19+
InvalidArgumentException::class,
20+
'Mailbox [invalid] is not defined. Please check your IMAP configuration.'
21+
);
22+
23+
test('it can watch mailbox', function () {
24+
Config::set('imap.mailboxes.test', [
25+
'host' => 'localhost',
26+
'port' => 993,
27+
'encryption' => 'ssl',
28+
'username' => '',
29+
'password' => '',
30+
]);
31+
32+
Imap::fake('test', folders: [
33+
new FakeFolder('inbox'),
34+
]);
35+
36+
App::bind(LoopInterface::class, LoopFake::class);
37+
38+
artisan(WatchMailbox::class, ['mailbox' => 'test'])->assertSuccessful();
39+
});

0 commit comments

Comments
 (0)