Створення нагадувача завдань за допомогою Laravel і MongoDB

Перекладено ШІ
Оригінал: Laravel News
Оновлено: 25 червня, 2025
Laravel стає все популярнішим серед розробників завдяки простому синтаксису, який дозволяє зосередитися на основних функціях застосунку. У цій статті ви дізнаєтеся, як інтегрувати MongoDB у Laravel для створення простої системи нагадувань про завдання – не проґавте цю можливість покращити свої навички!

Популярність Laravel останнім часом зросла, адже все більше розробників починають використовувати цю технологію. Laravel має зрозумілий синтаксис, що підвищує продуктивність, дозволяючи розробнику зосередитися на основних функціях свого додатку, не витрачаючи час на рутинні завдання, такі як автентифікація та надсилання електронних листів. У Laravel існує жвава спільнота користувачів і безліч навчальних матеріалів.

Новостворені програми на Laravel налаштовані на використання реляційних баз даних. Мета цього уроку — показати, як використовувати MongoDB у додатку Laravel. Ми розробимо просту систему нагадування про завдання для досягнення цієї мети.

# Попередні вимоги

Для ефективного проходження цього уроку вам знадобляться наступні інструменти та технології:

# Налаштування середовища

Щоб працювати з MongoDB, потрібно налаштувати наше середовище розробки. Переконайтеся, що всі необхідні залежності розробки встановлені та налаштовані. Вам знадобиться:

Налаштування проекту

Щоб розпочати створення нашої системи нагадування, перше, що потрібно зробити, це створити новий проект Laravel. Це можна зробити за допомогою Composer, виконуючи наступну команду:

composer create-project laravel/laravel LaravelMongodbProject
cd LaravelMongodbProject

Встановлення пакету MongoDB для Laravel

Новий проект Laravel за замовчуванням налаштований на реляційні бази даних, такі як MySql і PostgreSQL. MongoDB не підтримується в Laravel за замовчуванням, тому потрібно встановити пакет Laravel MongoDB та здійснити деяку конфігурацію у файлі config/database.php. Виконайте команду:

composer require mongodb/laravel-mongodb

Налаштуйте базу даних
Після завершення встановлення пакету MongoDB для Laravel, наступний крок — додати підключення до нашої бази даних MongoDB у файл config/database.php. Скопіюйте код нижче і вставте в масив connections, який містить конфігурації для інших типів баз даних:

return [
    'connections' => [
      'mongodb' => [
      'driver' => 'mongodb',
      'dsn' => env('MONGODB_URI'),
      'database' => 'YOUR_DATABASE_NAME',
    ],
    //Інші існуючі підключення залиште
  ],

Давайте пояснимо. Значення dsn береться з файлу .env. У файлі .env створіть значення для 'MONGODB_URI' та встановіть його у вигляді рядка підключення до MongoDB Atlas:

MONGODB_URI="<<MONGODB_ATLAS_CONNECTION_STRING>>"
DB_CONNECTION=mongodb

Автентифікація з Laravel Breeze
Ми встановили та налаштували наш додаток для роботи з MongoDB. Тепер давайте перейдемо до автентифікації. Laravel спрощує реалізацію автентифікації за допомогою пакетів, таких як Laravel Breeze, Laravel Fortify та Laravel Jetstream. У цьому уроці ми будемо використовувати Laravel Breeze для автентифікації. Для його встановлення використовуйте команду:

composer require laravel/breeze --dev

Після завершення встановлення виконайте наступні команди:

php artisan key:generate
 
php artisan breeze:install
php artisan migrate
 
php artisan db:seed
npm install
npm run dev

Ця команда запропонує вам вибрати свій стек і пакет тестування, як показано нижче. Для цілей цієї статті ми виберемо перший варіант (Blade та Alpine).

┌ Which Breeze stack would you like to install? ───────────────┐
│ > ● Blade with Alpine │
│ ○ Livewire (Volt Class API) with Alpine │
│ ○ Livewire (Volt Functional API) with Alpine │
│ ○ React with Inertia │
│ ○ Vue with Inertia │
│ ○ API only

Після цього буде додано автентифікаційні уявлення, маршрути, контролери та інші пов’язані ресурси у ваш додаток.
На даний момент давайте запустимо проект за допомогою вбудованого сервера Laravel і перевіримо, що все правильно працює. Використовуйте команду:

php artisan serve

Проект повинен працювати на 127.0.0.1:8000. Якщо порт 8000 вже зайнятий, Laravel перейде на новий доступний порт. Якщо все було зроблено правильно, ваш екран має виглядати як на зображенні нижче:

Ви можете увійти за такими обліковими даними: електронна адреса "test@example.com" та пароль "password".

Щоб перевірити, чи пакет Laravel MongoDB налаштовано правильно, давайте створимо маршрут для підключення до нашого кластера MongoDB. Додайте наступне в route/web.php:

Route::get('/ping', function (Request $request) {
  $connection = DB::connection('mongodb');
  try {
    $connection->command(['ping' => 1]);
    $msg = 'MongoDB доступний!';
  } catch (Exception $e) {
    $msg = 'Ви не підключені до MongoDB. Помилка: ' . $e->getMessage();
  }
  return ['msg' => $msg];
});

Перейдіть за цим маршрутом у браузері. Ваш екран має виглядати як на зображенні нижче, якщо все було зроблено правильно:

# Створення системи нагадування про завдання

Давайте створимо нашу модель і контролер для функції планування завдань. Для цього використовуйте команду:

php artisan make:model Task --resource --controller

Команда вище створить модель Task у директорії app/Models і контролер TaskController у директорії app/Http/Controllers з ресурсними методами.
Давайте створимо маршрут для TaskController. Перейдіть до routes/web.php і додайте наступне:

use App\Http\Controllers\TaskController;
Route::resource('tasks', TaskController::class)->middleware('auth');

Тепер давайте модифікуємо вміст моделі завдання на наші потреби. Перейдіть до app/Models/Task.php та замініть вміст на наступний:

<?php
namespace App\Models;
use MongoDB\Laravel\Eloquent\Model;
class Task extends Model
{
  protected $connection = 'mongodb';
  protected $table = 'tasks';
  protected $fillable = [
    'title', 'description', 'due_date', 'email', 'reminder_time', 'last_notification_date'
  ];
}

Вищенаведений код — це наша модель завдання.

Однією з унікальних особливостей MongoDB є те, що вона не потребує міграцій, як реляційні бази даних. Це означає, що ви можете безпосередньо додавати нові поля до своїх документів, не оновлюючи модель або створюючи міграції. Це особливо корисно для роботи з динамічними даними.
Тепер давайте модифікуємо наш контролер. Перейдіть до app/Http/Controllers/TaskController.php і оновіть вміст за допомогою коду нижче:

<?php
namespace App\Http\Controllers;
use App\Models\Task;
use Carbon\Carbon;
use Illuminate\Http\Request;
class TaskController extends Controller
{
    /**
    * Відображення списку ресурсів.
    */
    public function index()
    {
        $tasks = Task::where('email', auth()->user()->email)->get();
        return view('tasks.index', compact('tasks'));
    }
 
    /**
    * Показати форму для створення нового ресурсу.
    */
    public function create()
    {
        return view('tasks.create');
    }
 
    /**
    * Зберегти новостворений ресурс у сховищі.
    */
    public function store(Request $request)
    {
        $request->validate([
            'title' => 'required|string|max:255',
            'description' => 'nullable|string',
            'due_date' => 'required|date',
            'reminder_time' => 'required|date',
        ]);
 
        $data = $request->all();
        $data['due_date'] = Carbon::parse($request->due_date);
        $data['reminder_time'] = Carbon::parse($request->reminder_time);
        $data['email'] = auth()->user()->email;
 
        $data['last_notification_date'] = null;
 
        Task::create($data);
 
        return redirect()->route('tasks.index')->with('success', 'Завдання успішно створено.');
    }
 
    /**
    * Відобразити вказаний ресурс.
    */
    public function show(string $id)
    {
        //
    }
 
    /**
    * Показати форму для редагування вказаного ресурсу.
    */
    public function edit(string $id)
    {
        $tasks = Task::where('id', $id)->get();
        return view('tasks.edit', ['tasks' => $tasks]);
    }
 
    /**
    * Оновити вказаний ресурс у сховищі.
    */
    public function update(Request $request, string $id)
    {
        $task = Task::findOrFail($id);
        $data = $request->all();
 
        $data['due_date'] = Carbon::parse($request->due_date)->format('Y-m-d H:i:s');
        $data['reminder_time'] = Carbon::parse($request->reminder_time)->format('Y-m-d H:i:s');
        $task->update($data);
 
        return redirect()->route('tasks.index')->with('success', 'Завдання успішно оновлено.');
    }
 
    /**
    * Видалити вказаний ресурс зі сховища.
    */
    public function destroy(string $id)
    {
        $task = Task::findOrFail($id);
        $task->delete();
        return redirect()->route('tasks.index')->with('success', 'Завдання успішно видалено.');
    }
}

Наш новостворений TaskController містить код, який обробляє CRUD-операції нашої моделі завдання. Метод index отримує всі завдання, що належать увімкненому користувачеві, і передає їх до файлу index.blade.php для відображення на фронті.

Метод create повертає форму для створення нового завдання, а метод store перевіряє вхідні дані, призначає електронну адресу увімкненого користувача завданню та зберігає його в базі даних.
Для оновлень, метод edit отримує конкретне завдання для редагування та відображає його у формі редагування. Коли ця форма надсилається, вона викликає метод update, який зберігає відредаговане завдання в колекції завдань MongoDB.

Метод destroy видаляє конкретне завдання. Кожна операція перенаправляє назад до списку завдань з повідомленням про успішність для зворотного зв’язку користувача.

Давайте створимо файли вигляду для нашого планувальника завдань. У директорії resources/views створіть папку з назвою /tasks та створіть у ній такі файли:

Відкрийте resources/views/create.blade.php та замініть вміст сторінки на:

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Завдання') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="container mx-auto p-4">
                    <h2 class="text-2xl font-bold mb-4">Створити нове завдання</h2>
                    <form action="{{ route('tasks.store') }}" method="POST">
                        @csrf
                        <div class="mb-4">
                            <label for="title" class="block text-gray-700">Назва:</label>
                            <input type="text" name="title" id="title" required class="border border-gray-300 p-2 w-full" value="{{ old('title') }}">
                            @error('title')
                            <p class="text-red-500">{{ $message }}</p>
                            @enderror
                        </div>
                        <div class="mb-4">
                            <label for="description" class="block text-gray-700">Опис:</label>
                            <textarea name="description" id="description" class="border border-gray-300 p-2 w-full">{{ old('description') }}</textarea>
                        </div>
                        <div class="mb-4">
                            <label for="due_date" class="block text-gray-700">Дедлайн:</label>
                            <input type="date" name="due_date" id="due_date" required class="border border-gray-300 p-2 w-full" value="{{ old('due_date') }}">
                            @error('due_date')
                            <p class="text-red-500">{{ $message }}</p>
                            @enderror
                        </div>
                        <div class="mb-4">
                            <label for="reminder_time" class="block text-gray-700">Час нагадування:</label>
                            <input type="datetime-local" name="reminder_time" id="reminder_time" class="border border-gray-300 p-2 w-full" value="{{ old('reminder_time') }}">
                        </div>
                        <button type="submit" class="bg-green-600 text-white text-gray-399 p-2 border rounded">Створити завдання</button>
                    </form>
                </div>
            </div>
        </div>
    </div>
</x-app-layout>

У наведеному коді ми додали HTML форму створення. Форма містить текстове поле для назви та опису, а також поля дати та часу для дедлайну та часу нагадування. Якщо ваш код правильний, ваш екран має виглядати як на зображенні нижче.

Повторіть те саме для edit.blade.php. Перейдіть до resources/views/edit.blade.php і додайте код нижче:

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Редагувати завдання') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 text-gray-900">
                    @foreach($tasks as $task)
                        <form action="{{ route('tasks.update', $task->id) }}" method="POST">
                            @csrf
                            @method('PUT')
                            <div class="mb-4">
                                <label for="title" class="block text-gray-700">Назва:</label>
                                <input type="text" name="title" id="title" required class="border border-gray-300 p-2 w-full" value="{{ old('title', $task->title) }}">
                                @error('title')
                                <p class="text-red-500">{{ $message }}</p>
                                @enderror
                            </div>
                            <div class="mb-4">
                                <label for="description" class="block text-gray-700">Опис:</label>
                                <textarea name="description" id="description" class="border border-gray-300 p-2 w-full">{{ old('description', $task->description) }}</textarea>
                            </div>
                            <div class="mb-4">
                                <label for="due_date" class="block text-gray-700">Дедлайн:</label>
                                <input type="date" name="due_date" id="due_date" required class="border border-gray-300 p-2 w-full" value="{{ old('due_date', $task->due_date) }}">
                                @error('due_date')
                                <p class="text-red-500">{{ $message }}</p>
                                @enderror
                            </div>
                            <div class="mb-4">
                                <label for="reminder_time" class="block text-gray-700">Час нагадування:</label>
                                <input type="datetime-local" name="reminder_time" id="reminder_time" class="border border-gray-300 p-2 w-full" value="{{ old('reminder_time', $task->reminder_time) }}">
                            </div>
                            <button type="submit" class="bg-blue-500 text-white p-2 rounded">Оновити завдання</button>
                        </form>
                    @endforeach
                </div>
            </div>
        </div>
    </div>
</x-app-layout>

Форма edit містить ті самі поля введення, що й форма create вище. Вона заповнена даними завдання, яке редагується.

Наостанок перейдіть до resources/views/index.blade.php і замініть вміст на код нижче:

<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            {{ __('Завдання') }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 text-gray-900">
                    <div class="mb-2">
                        <a href="{{ route('tasks.create') }}" class="p-2 border mb-4">Створити нове завдання</a>
                    </div>
                    <ul class="mt-4">
                        @foreach ($tasks as $task)
                            <div class="mt-2">
                                <hr>
                            </div>
                            <li>
                                <h1 class="text-2xl">
                                    <strong>{{ $task->title }}</strong> - Дедлайн: {{ $task->due_date }}
                                </h1>
                                <p class="text-gray-600">
                                    {{ $task->description }}
                                </p>
                                <div class="flex gap-2 mt-4">
                                    <div class="p-2 text-white bg-gray-700">
                                        <a href="{{ route('tasks.edit', $task->id) }}">Редагувати</a>
                                    </div>
                                    <div class="p-2 text-white bg-red-700 rounded">
                                        <form action="{{ route('tasks.destroy', $task->id) }}" method="POST" style="display:inline;">
                                            @csrf
                                            @method('DELETE')
                                            <button type="submit">Видалити</button>
                                        </form>
                                    </div>
                                </div>
                            </li>
                        @endforeach
                    </ul>
                </div>
            </div>
        </div>
    </div>
</x-app-layout>

У наведеному коді ми додали посилання на форму create. Він також проходить через всі нагадування і відображає ті, що належать цьому користувачеві.

Додати маршрут

Тепер нам потрібно додати посилання на функцію завдань у навігації нашого додатка. Відкрийте resources/views/layouts/navigation.blade.php і додайте наступний рядок коду одразу після посилання на панель приладів.

//...існуючий код ...
<div class="hidden space-x-8 sm:-my-px sm:ms-10 sm:flex">
    <x-nav-link :href="route('tasks.index')":active="request()->routeIs('tasks.index')">
        {{ __('Завдання') }}
    </x-nav-link>
</div>

На цьому етапі ми повинні протестувати CRUD-операції нашої системи керування завданнями. Переконайтеся, що все працює правильно, перш ніж переходити до наступного розділу.
Налаштування нагадувань з використанням завдань Laravel
Давайте перейдемо до реалізації системи нагадування для термінових завдань. Для цього ми:

php artisan make:command SendTaskReminders

Після створення команди, оновіть вміст файлу app/Console/Commands/SendTaskReminders.php кодом нижче:

<?php
 
namespace App\Console\Commands;
 
use App\Models\Task;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Mail;
 
class SendTaskReminders extends Command
{
    /**
    * Назва та підпис команди консолі.
    *
    * @var string
    */
    protected $signature = 'app:send-task-reminders';
 
    /**
    * Опис команди консолі.
    *
    * @var string
    */
    protected $description = 'Опис команди';
 
    /**
    * Виконати команду консолі.
    */
    public function handle()
    {
        $now = Carbon::now();
        $upcomingTasks = Task::where('last_notification_date', null)->get();
 
        $upcomingTasks = Task::where('last_notification_date', null)
        ->where('reminder_time', '>=', $now->clone()->subMinutes(10))
        ->get();
 
        foreach ($upcomingTasks as $task) {
            $emailBody = <<title}
              Опис: {$task->description}
              Дедлайн: {$task->due_date}
              Будь ласка, переконайтеся, що ви завершите його вчасно!
              З повагою,
              Ваш додаток нагадування про завдання
              EOF;
 
            Mail::raw($emailBody, function ($message) use ($task) {
                $message->to($task->email)
                ->subject("Нагадування про завдання: {$task->title}");
            });
 
            $task->last_notification_date = $now;
            $task->save();
            $this->info("Лист-нагадування надіслано для завдання: {$task->title}");
        }
 
        return self::SUCCESS;
    }
}

Основна логіка нашої команди Artisan написана в методі handle(). У коді вище ми отримуємо поточний штамп часу за допомогою Carbon::now().

Далі ми запитуємо базу даних, щоб отримати всі завдання, де reminder_time менше або дорівнює поточному часу (значення $now) та де reminder_time більше або дорівнює 10 хвилинам до цього часу, з цією рядком коду: 'reminder_time','>=',$now->clone()->subMinutes(10). У MongoDB всі дати зберігаються в UTC. Навіть якщо ваш сервер використовує інший часовий пояс, вам не потрібно цього змінювати.

Для більшого пояснення, припустимо, що поточний час $now = 2024-10-22 15:00:00. Запит на нагадування про завдання отримує всі завдання між 2024-10-22 14:50:00 та 2024-10-22 15:00:00. Ми отримуємо всі завдання, термін виконання яких настане через 10 хвилин.
Потім ми проходимо через результат і надсилаємо нагадування електронною поштою користувачам про завдання, які будуть терміновими протягом наступних 10 хвилин.

Щоб планувальник завдань працював правильно, ви повинні налаштувати свій додаток на відправку електронних листів.
Mailtrap.io — чудовий інструмент для тестування надсилання електронних листів. Отримайте детальну інформацію про як налаштувати свій додаток для відправки електронних листів за допомогою Mailtrap.

Планування сповіщень для нагадувань про завдання

Щоб закрити все, нам потрібно запланувати виконання команди Artisan, яку ми створили вище, на кожну хвилину. Вважайте це способом автоматичного запуску php artisan app:send-task-reminders щоранку.
Є кілька способів запланювати виконання завдання в Laravel. У цьому уроці ми працюємо з версією Laravel 11.9, яка є найновішою версією на момент написання цього матеріалу. Щоб продовжити, перейдіть до routes/console.php і додайте наступне:

//...існуючий код ...
Schedule::command('app:send-task-reminders')->everyMinute();

Щоб перевірити, чи це працює, виконайте команду:

php artisan schedule:run

На виробничому сервері вам потрібно налаштувати завдання cron для виконання команди php artisan schedule:run через регулярні інтервали.
У сервері на базі Linux або Unix ви можете відкрити файл конфігурації cron, використовуючи команду:

crontab -e

Додайте код нижче до вкладки конфігурації cron:

Для правильного виконання замініть /usr/bin/php шляхом до вашого PHP-бінарного файлу та /path-to-your-project на повний шлях до вашого проекту Laravel на сервері, а потім збережіть і вийдіть.
Ви можете переконатися, що все працює, набравши цю команду:

crontab -l

Висновок

Ми підійшли до завершення цього уроку; чудова робота, якщо ви слідкували за нами. Для підсумку, в цьому уроці ми пройшли процес створення планувальника завдань у Laravel та MongoDB. Декілька ключових елементів реалізації:

Знайдіть проект на GitHub. Не соромтеся клонувати його, зареєструватися на MongoDB Atlas та налаштувати його під ваші потреби. Для додаткової підтримки приєднуйтесь до спільноти розробників MongoDB.