Courses

Laravel User Timezones Project: Convert, Display, Send Notifications

Adding Scheduled Notifications to DB

Scheduling Notifications is as easy as creating a new entry in the scheduled_notifications table. We'll do that in our Controller:


Modifying the Controller

app/Http/Controllers/BookingController.php:

use App\Http\Requests\StoreBookingRequest;
use App\Http\Requests\UpdateBookingRequest;
use App\Models\Booking;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
 
class BookingController extends Controller
{
// ...
 
public function store(StoreBookingRequest $request): RedirectResponse
{
$booking = $request->user()->bookings()->create([
'start' => fromUserDateTime($request->validated('start'), $request->user()),
'end' => fromUserDateTime($request->validated('end'), $request->user()),
]);
$startTime = CarbonImmutable::parse(toUserDateTime($booking->start, $booking->user), $booking->user->timezone);
 
// Schedule 1H reminder
$oneHourTime = fromUserDateTime($startTime->subHour(), $booking->user);
if (now('UTC')->lessThan($oneHourTime)) {
$booking->user->scheduledNotifications()->create([
'notification_class' => BookingReminder1H::class,
'notifiable_id' => $booking->id,
'notifiable_type' => Booking::class,
'sent' => false,
'processing' => false,
'scheduled_at' => $oneHourTime,
'sent_at' => null,
'tries' => 0,
]);
}
 
return redirect()->route('booking.index');
}
 
// ...
 
public function update(UpdateBookingRequest $request, Booking $booking): RedirectResponse
{
$booking->update([
'start' => fromUserDateTime($request->validated('start'), $request->user()),
'end' => fromUserDateTime($request->validated('end'), $request->user()),
]);
 
$startTime = CarbonImmutable::parse(toUserDateTime($booking->start, $booking->user), $booking->user->timezone);
 
$hasScheduledNotifications = ScheduledNotification::query()
->where('notifiable_id', $booking->id)
->where('notifiable_type', Booking::class)
->where('user_id', $booking->user_id)
->exists();
 
// First we need to check if there are any already scheduled notifications
if ($hasScheduledNotifications) {
// Then in this example, we simply delete them. You can however update them if you want.
$booking->scheduledNotifications()
->where('user_id', $booking->user_id)
->delete();
}
 
// Since we are clearing the scheduled notifications, we need to create them again for the new date
// Schedule 1H reminder
$oneHourTime = fromUserDateTime($startTime->subHour(), $booking->user);
if (now('UTC')->lessThan($oneHourTime)) {
$booking->user->scheduledNotifications()->create([
'notification_class' => BookingReminder1H::class,
'notifiable_id' => $booking->id,
'notifiable_type' => Booking::class,
'sent' => false,
'processing' => false,
'scheduled_at' => $oneHourTime,
'sent_at' => null,
'tries' => 0,
]);
}
 
return redirect()->route('booking.index');
}
 
public function destroy(Request $request, Booking $booking): RedirectResponse
{
abort_unless($booking->user_id === $request->user()->id, 404);
 
$booking->delete();
 
$booking->scheduledNotifications()
->where('user_id', $booking->user_id)
->delete();
 
return redirect()->route('booking.index');
}
}

This is it, now if we create a new booking, we will have a scheduled Notification for 1H before the booking starts:

Next up, we will send the Notifications out!

Previous: Creating Laravel Notification Class
avatar

Haveing a little dificalty hear In the github laravel-timezones-course-lesson-3-SendingScheduledNotifications

The above $startTime = CarbonImmutable::parse(toUserDateTime($booking->start, $booking->user), $booking->user->timezone); is in the Models\Booking.php file not the BookingController.php as you have it hear

Content of the BookingController.php file in the GitHub file I downlowed:

avatar

Could you rephrase the issue you are facing here? Not sure I understood.

avatar
You can use Markdown
avatar

tryed to get the copy of the gethub repository I downloaded to work however I am getting errors dealing with the phpunit file:

  • Installing phpunit/php-code-coverage (10.1.2): Cloning db1497ec8d from cache db1497ec8dd382e82c962f7abbe0320e4882ee4e is gone (history was rewritten?) Install of phpunit/php-code-coverage failed

    • Installing phpunit/phpunit (10.2.1): Cloning 599b332943 from cache 599b33294350e8f51163119d5670512f98b0490d is gone (history was rewritten?) Install of phpunit/phpunit failed

      In GitDownloader.php line 500:
      

    Failed to execute git checkout db1497ec8dd382e82c962f7abbe0320e4882ee4e -- && git reset --hard db1497ec8dd382e82
    c962f7abbe0320e4882ee4e --

    HEAD is now at db1497ec Prepare release
    error: unable to create file tests/_files/Report/HTML/CoverageForClassWithAnonymousFunction/source_with_class_an
    d_anonymous_function.php.html: Filename too long
    ate phpunit/php-code-coverage" to resolve this.

the system tryed to update to Laravel 10.13.2 the latest out as of this time. 6/6/2023 4:14 PM

Help !! ??

avatar

I'm not sure why you are getting this, but it seems like you are trying to run phpunit with code coverage. Don't use that flag and it should work just fine.

avatar

I was able to re-download from the GitHub repository and it works just fine. I even got the version I am creating to pass the phpUnit test all went well up to this point. Above you have us entering what I find in the class Booking extends Model of the download and not in the Booking Controller where you have us putting it. Even then they are not the same Hear you have: Public function store(StoreBookingRequest $request): RedirectResponse { $booking = $request->user()->bookings()->create($request->validated()); $startTime = CarbonImmutable::parse(toUserDateTime($booking->start, $booking->user), $booking->user->timezone); // Schedule 1H reminder $oneHourTime = fromUserDateTime($startTime->subHour(), $booking->user); if (now('UTC')->lessThan($oneHourTime)) { $booking->user->scheduledNotifications()->create([ 'notification_class' => BookingReminder1H::class, 'notifiable_id' => $booking->id, 'notifiable_type' => Booking::class, 'sent' => false, 'processing' => false, 'scheduled_at' => $oneHourTime, 'sent_at' => null, 'tries' => 0, ]); } Return redirect()->route(‘booking.index’); }

And on the GitHub repository you have

public function createReminderNotifications(Booking $booking, CarbonImmutable $startTime): void { // Schedule 2H reminder $twoHoursTime = fromUserDateTime($startTime->subHours(2), $booking->user); if (now('UTC')->lessThan($twoHoursTime)) { $booking->user->scheduledNotifications()->create([ 'notification_class' => BookingReminder2H::class, 'notifiable_id' => $booking->id, 'notifiable_type' => CLASS, 'sent' => false, 'processing' => false, 'scheduled_at' => $twoHoursTime, 'sent_at' => null, 'tries' => 0, ]); } // Schedule 1H reminder $oneHourTime = fromUserDateTime($startTime->subHour(), $booking->user); if (now('UTC')->lessThan($oneHourTime)) { $booking->user->scheduledNotifications()->create([ 'notification_class' => BookingReminder1H::class, 'notifiable_id' => $booking->id, 'notifiable_type' => CLASS, 'sent' => false, 'processing' => false, 'scheduled_at' => $oneHourTime, 'sent_at' => null, 'tries' => 0, ]); } // Schedule 5 min reminder $fiveMinutesTime = fromUserDateTime($startTime->subMinutes(5), $booking->user); if (now('UTC')->lessThan($fiveMinutesTime)) { $booking->user->scheduledNotifications()->create([ 'notification_class' => BookingReminder5MIN::class, 'notifiable_id' => $booking->id, 'notifiable_type' => CLASS, 'sent' => false, 'processing' => false, 'scheduled_at' => $fiveMinutesTime, 'sent_at' => null, 'tries' => 0, ]); } // Schedule started reminder $startingTime = fromUserDateTime($startTime, $booking->user); if (now('UTC')->lessThan($startingTime)) { $booking->user->scheduledNotifications()->create([ 'notification_class' => BookingStartedNotification::class, 'notifiable_id' => $booking->id, 'notifiable_type' => CLASS, 'sent' => false, 'processing' => false, 'scheduled_at' => $startingTime, 'sent_at' => null, 'tries' => 0, ]); } }

Remember this is in the class Booking extends Model

Not the class BookingController extends Controller; hear you have:

public function store(StoreBookingRequest $request): RedirectResponse { $booking = $request->user()->bookings()->create([ 'start' => fromUserDateTime($request->validated('start'), $request->user()), 'end' => fromUserDateTime($request->validated('end'), $request->user()), ]);

    event(new BookingCreatedEvent($booking));

    return redirect()->route('booking.index');
}

What should I do? keep following the GitHub download that I know works! Or you from hear?

avatar

There's so much written here but... I understood that the tutorial has skipped these lines:

[
		'start' => fromUserDateTime($request->validated('start'), $request->user()),
		'end' => fromUserDateTime($request->validated('end'), $request->user()),
]

So I've added that. If There's anything else I've missed - please correct the formatting and I'll take a look!

avatar

We are using the toUserDateTime function to transform the start date from UTC to user timezone (in your case, it would be UTC + 7). This is required as we need to have the notification specifically for that users timezone.

As for why $data['start'] is UTC - that's because it does not do automatic transformation. You could enable it with cast on your model to tell that it should be a Carbon instance, but that could bring some issues at a later point. So I like to keep them as strings and mutate manually (easier to debug) :)

avatar

I see, I think we can change the code from $startTime = CarbonImmutable::parse(toUserDateTime($booking->start, $booking->user), $booking->user->timezone); to $startTime = CarbonImmutable::parse($request->start). As I tested it, it produce the same result

avatar

Could be! there's many ways to write the same thing!

avatar

Class "App\Models\ScheduledNotification" not found while there is code written in \app\Models\User

use App\Models\ScheduledNotification;

public function scheduledNotifications(): HasMany { return $this->hasMany(ScheduledNotification::class); }

	what is wrong there?
avatar
You can use Markdown
avatar
You can use Markdown