Skip to content

Commit 1e4909c

Browse files
CaReS0107Vasile Papuc
andauthored
Add hours precision when email is dispatched (#2576)
* Add hour precision when mailator is dispached * Added readme documentation & fix tests * Fix styling --------- Co-authored-by: Vasile Papuc <[email protected]> Co-authored-by: CaReS0107 <[email protected]>
1 parent afa2b77 commit 1e4909c

File tree

10 files changed

+145
-10
lines changed

10 files changed

+145
-10
lines changed

.github/workflows/run-tests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,4 @@ jobs:
4040
composer update --${{ matrix.stability }} --prefer-dist --no-interaction
4141
4242
- name: Execute tests
43-
run: ./vendor/bin/testbench package:test --parallel --no-coverage
43+
run: ./vendor/bin/testbench package:test

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,26 @@ The `after` constraint accept a `CarbonInterface` as well. The difference, is th
9999
->after($order->created_at)
100100
```
101101

102+
### Precision
103+
Hour Precision
104+
105+
The `precision` method provides fine-grained control over when emails are sent using MailatorSchedule. It allows you to specify specific hours or intervals within a 24-hour period. Here's an example of how to use the precision method:
106+
```php
107+
->many()
108+
->precision([3-4])
109+
```
110+
This will schedule the email dispatch between '03:00:00' AM and '04:59:59' AM.
111+
112+
or
113+
```php
114+
->once()
115+
->precision([1])
116+
```
117+
This will schedule the email dispatch between '01:00:00' AM and '01:59:59'.
118+
119+
You can continue this pattern to specify the desired hour(s) within the range of 1 to 24.
120+
121+
**Important: When using the precision feature in the Mailator scheduler, it is recommended to set the scheduler to run at intervals that are less than an hour. You can choose intervals such as every 5 minutes, 10 minutes, 30 minutes, or any other desired duration.**
102122
### Constraint
103123

104124
The `constraint()` method accept an instance of `Binarcode\LaravelMailator\Constraints\SendScheduleConstraint`. Each constraint will be called when the scheduler will try to send the email. If all constraints return true, the email will be sent.

database/migrations/create_mailator_tables.php.stub

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class CreateMailatorTables extends Migration
3030
$table->json('recipients')->nullable();
3131
$table->text('when')->nullable();
3232
$table->string('frequency_option')->default(MailatorSchedule::FREQUENCY_OPTIONS_ONCE)->comment('How often send email notification.');
33-
33+
$table->json('schedule_at_hours')->nullable();
3434
$table->timestamp('last_sent_at')->nullable();
3535
$table->timestamp('last_failed_at')->nullable();
3636
$table->timestamp('completed_at')->nullable();

src/Actions/RunSchedulersAction.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class RunSchedulersAction
99
{
1010
use ClassResolver;
1111

12-
public function __invoke()
12+
public function __invoke(): void
1313
{
1414
static::scheduler()::query()
1515
->ready()
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace Binarcode\LaravelMailator\Constraints;
4+
5+
use Binarcode\LaravelMailator\Models\MailatorSchedule;
6+
use Illuminate\Support\Collection;
7+
8+
class HoursSchedulerCheckerConstraint implements SendScheduleConstraint
9+
{
10+
public function canSend(MailatorSchedule $schedule, Collection $logs): bool
11+
{
12+
if (! $schedule->hasPrecision()) {
13+
return true;
14+
}
15+
16+
return in_array(now()->hour, $schedule->schedule_at_hours);
17+
}
18+
}

src/Models/Concerns/ConstraintsResolver.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Binarcode\LaravelMailator\Constraints\BeforeConstraint;
88
use Binarcode\LaravelMailator\Constraints\DailyConstraint;
99
use Binarcode\LaravelMailator\Constraints\Descriptionable;
10+
use Binarcode\LaravelMailator\Constraints\HoursSchedulerCheckerConstraint;
1011
use Binarcode\LaravelMailator\Constraints\ManualConstraint;
1112
use Binarcode\LaravelMailator\Constraints\ManyConstraint;
1213
use Binarcode\LaravelMailator\Constraints\NeverConstraint;
@@ -34,6 +35,7 @@ public function configurationsPasses(): bool
3435
ManyConstraint::class,
3536
DailyConstraint::class,
3637
WeeklyConstraint::class,
38+
HoursSchedulerCheckerConstraint::class,
3739
])
3840
->map(fn ($class) => app($class))
3941
->every(fn (SendScheduleConstraint $event) => $event->canSend($this, $this->logs));
@@ -49,7 +51,10 @@ public function eventsPasses(): bool
4951
return collect($this->constraints)
5052
->map(fn (string $event) => unserialize($event))
5153
->filter(fn ($event) => is_subclass_of($event, SendScheduleConstraint::class))
52-
->filter(fn (SendScheduleConstraint $event) => $event->canSend($this, $this->logs))->count() === collect($this->constraints)->count();
54+
->filter(fn (SendScheduleConstraint $event) => $event->canSend(
55+
$this,
56+
$this->logs
57+
))->count() === collect($this->constraints)->count();
5358
}
5459

5560
public function constraintsDescriptions(): array

src/Models/MailatorSchedule.php

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
* @property Carbon $last_failed_at
5454
* @property string $failure_reason
5555
* @property Carbon $last_sent_at
56+
* @property array|null $schedule_at_hours
5657
* @property Carbon $completed_at
5758
* @property string $frequency_option
5859
* @property-read Collection $logs
@@ -88,6 +89,7 @@ public function getTable()
8889
protected $casts = [
8990
'constraints' => 'array',
9091
'recipients' => 'array',
92+
'schedule_at_hours' => 'array',
9193
'timestamp_target' => 'datetime',
9294
'last_failed_at' => 'datetime',
9395
'last_sent_at' => 'datetime',
@@ -244,6 +246,11 @@ public function isWeekly(): bool
244246
return $this->frequency_option === static::FREQUENCY_OPTIONS_WEEKLY;
245247
}
246248

249+
public function hasPrecision(): bool
250+
{
251+
return (bool) $this->schedule_at_hours;
252+
}
253+
247254
public function isAfter(): bool
248255
{
249256
return $this->time_frame_origin === static::TIME_FRAME_ORIGIN_AFTER;
@@ -307,6 +314,13 @@ public function days(int $number): self
307314
return $this;
308315
}
309316

317+
public function precision(array $scheduleAtHours): self
318+
{
319+
$this->schedule_at_hours = $scheduleAtHours;
320+
321+
return $this;
322+
}
323+
310324
public function weeks(int $number): static
311325
{
312326
$this->delay_minutes = $number * ConverterEnum::MINUTES_IN_WEEK;
@@ -368,7 +382,7 @@ public function shouldSend(): bool
368382
}
369383

370384
return true;
371-
} catch (Exception | Throwable $e) {
385+
} catch (Exception|Throwable $e) {
372386
$this->markAsFailed($e->getMessage());
373387

374388
app(ResolveGarbageAction::class)->handle($this);
@@ -408,7 +422,7 @@ public function execute(bool $now = false): void
408422
dispatch(new SendMailJob($this));
409423
}
410424
}
411-
} catch (Exception | Throwable $e) {
425+
} catch (Exception|Throwable $e) {
412426
$this->markAsFailed($e->getMessage());
413427
}
414428
}
@@ -427,7 +441,7 @@ public function getMailable(): ?Mailable
427441
{
428442
try {
429443
return unserialize($this->mailable_class);
430-
} catch (Throwable | TypeError $e) {
444+
} catch (Throwable|TypeError $e) {
431445
$this->markAsFailed($e->getMessage());
432446
}
433447

@@ -494,7 +508,7 @@ public function actionClass(Action $action): self
494508
return $this;
495509
}
496510

497-
public function tag(string | array $tag): self
511+
public function tag(string|array $tag): self
498512
{
499513
if (is_array($tag)) {
500514
$tag = implode(',', $tag);

tests/Feature/Models/MailatorScheduleTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public function test_can_use_carbon_target_date_before(): void
6262

6363
// MailatorSchedule::run();
6464
// Mail::assertNothingSent();
65-
65+
6666
$this->travelTo(now()->addDays(6));
6767
MailatorSchedule::run();
6868

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
namespace Binarcode\LaravelMailator\Tests\Feature;
4+
5+
use Binarcode\LaravelMailator\Models\MailatorSchedule;
6+
use Binarcode\LaravelMailator\Tests\Fixtures\InvoiceReminderMailable;
7+
use Binarcode\LaravelMailator\Tests\TestCase;
8+
use Carbon\Carbon;
9+
use Illuminate\Support\Facades\Mail;
10+
use Spatie\TestTime\TestTime;
11+
12+
class WithWeekendsConstraintTest extends TestCase
13+
{
14+
public function test_can_send_mail_with_precision_at_the_given_hour(): void
15+
{
16+
Mail::fake();
17+
Mail::assertNothingSent();
18+
19+
MailatorSchedule::init('Invoice reminder.')
20+
->recipients([
21+
22+
])
23+
->mailable(
24+
(new InvoiceReminderMailable())->to('[email protected]')
25+
)
26+
->precision([5])
27+
->save();
28+
29+
MailatorSchedule::run();
30+
Mail::assertNotSent(InvoiceReminderMailable::class);
31+
32+
$this->travelTo(Carbon::parse('05:00:00'));
33+
MailatorSchedule::run();
34+
35+
Mail::assertSent(InvoiceReminderMailable::class);
36+
37+
$this->travelTo(Carbon::parse('06:00:00'));
38+
39+
MailatorSchedule::run();
40+
41+
Mail::assertSent(InvoiceReminderMailable::class, 1);
42+
}
43+
44+
public function test_can_set_precision_in_interval(): void
45+
{
46+
TestTime::freeze();
47+
48+
Mail::fake();
49+
Mail::assertNothingSent();
50+
51+
MailatorSchedule::init('Invoice reminder.')
52+
->recipients([
53+
54+
])
55+
->mailable(
56+
(new InvoiceReminderMailable())->to('[email protected]')
57+
)
58+
->many()
59+
->precision([1, 2])
60+
->save();
61+
62+
$this->travelTo(Carbon::parse('12:00:00'));
63+
MailatorSchedule::run();
64+
Mail::assertNotSent(InvoiceReminderMailable::class);
65+
66+
$this->travelTo(Carbon::parse('01:00:00'));
67+
MailatorSchedule::run();
68+
Mail::assertSent(InvoiceReminderMailable::class);
69+
70+
$this->travelTo(Carbon::parse('02:59:59'));
71+
MailatorSchedule::run();
72+
Mail::assertSent(InvoiceReminderMailable::class);
73+
74+
$this->travelTo(Carbon::parse('03:00:00'));
75+
MailatorSchedule::run();
76+
77+
Mail::assertSent(InvoiceReminderMailable::class, 2);
78+
}
79+
}

tests/TestCase.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
use Illuminate\Contracts\View\Factory;
77
use Mockery as m;
88
use Orchestra\Testbench\TestCase as Orchestra;
9-
use Swift_Mailer;
109

1110
class TestCase extends Orchestra
1211
{

0 commit comments

Comments
 (0)