- Введение
- Требования к проекту
- Установка и настройка проекта
- Настройка библиотеки webklex/laravel-imap
- Отправка тестового сообщения на почту
- Создание модели и миграции для хранения файлов
- Создание модели и миграции для настройки для загрузки файлов
- Парсер писем и загрузка файлов
- Шаг 1: Создание класса сервиса
- Шаг 2: Настройка конструктора и методов
- Интерфейс для загрузки файла
- Запуск скрипта
- Заключение
Введение
В этой статье мы рассмотрим, как создать скрипт на PHP для загрузки файлов из электронной почты в базу данных. Мы будем использовать PHP 8.2, фреймворк Laravel и библиотеку webklex/laravel-imap для парсинга почты. Этот подход может быть полезен для автоматизации обработки файлов, которые приходят на электронную почту, и их сохранения в базе данных.
Требования к проекту
Для реализации данного проекта нам понадобятся следующие компоненты:
- PHP 8.2 – современная версия языка программирования PHP.
- Laravel – популярный фреймворк для разработки веб-приложений на PHP.
- Filament – административная панель для Laravel, которая упрощает разработку интерфейсов управления приложением.
- webklex/laravel-imap – библиотека для работы с IMAP-серверами, позволяющая легко получать доступ к почтовым ящикам.
Установка и настройка проекта
Начнем с создания нового проекта на основе Laravel. Откройте терминал и выполните следующую команду:
1 | composer create-project laravel/laravel myproject |
Это создаст новый проект Laravel в директории myproject
. После этого перейдем внутрь созданной директории и установим необходимые пакеты:
1 2 3 4 | cd myproject composer require filament/filament composer require webklex/laravel-imap php artisan filament:install --panels |
Теперь настроим подключение к базе данных. Откройте файл .env
и добавьте настройки подключения к вашей базе данных:
1 2 3 4 5 6 | DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=your_database_name DB_USERNAME=your_username DB_PASSWORD=your_password |
После этого выполните миграцию базы данных:
1 | php artisan migrate |
Вы можете создать новую учетную запись пользователя с помощью следующей команды:
1 | php artisan make:filament-user |
Настройка библиотеки webklex/laravel-imap
Добавьте соответствующие переменные окружения в ваш файл .env
:
1 2 3 4 5 6 7 8 | IMAP_HOST=imap.gmail.com IMAP_PORT="143" IMAP_ENCRYPTION="" IMAP_VALIDATE_CERT="false" IMAP_USERNAME=your_email@example.com IMAP_PASSWORD=your_password IMAP_DEFAULT_ACCOUNT=default IMAP_PROTOCOL=imap |
Отправка тестового сообщения на почту
Допустим у нас на почте в папке есть письмо, и нам нужно сохранить вложение в бд

И содержимое файла

Создание модели и миграции для хранения файлов
Создадим модель и миграцию для таблицы, где будут храниться загружаемые файлы. Выполните следующую команду:
1 | php artisan make:model File -m |
Откройте созданный файл миграции и добавьте поля для хранения имени файла и пути к нему:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; class CreateFilesTable extends Migration { public function up() { Schema::create('files', function (Blueprint $table) { $table->id(); $table->string('value')->nullable(); $table->string('code')->nullable(); $table->string('DateS')->nullable(); $table->timestamps(); }); } public function down() { Schema::dropIfExists('files'); } } |
И заполним модель:
1 2 3 4 5 6 7 8 9 10 | <?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class File extends Model { protected $guarded = []; } |
Создание модели и миграции для настройки для загрузки файлов
Создадим модель и миграцию для таблицы, где будут храниться загружаемые файлы. Выполните следующую команду:
1 | php artisan make:model ImportSetting -m |
Откройте созданный файл миграции и добавьте поля для хранения имени файла и пути к нему:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { /** * Run the migrations. */ public function up(): void { Schema::create('import_settings', function (Blueprint $table) { $table->id(); $table->string('name', 191); $table->string('file_name', 191); $table->string('mailbox_folder',191); $table->string('import_model', 191); $table->timestamps(); }); } /** * Reverse the migrations. */ public function down(): void { Schema::dropIfExists('import_settings'); } }; |
Ну и модель к нему:
1 2 3 4 5 6 7 8 9 10 | <?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class ImportSetting extends Model { protected $guarded = []; } |
Выполним миграцию:
1 | php artisan migrate |
Парсер писем и загрузка файлов
Шаг 1: Создание класса сервиса
Создайте новый PHP-файл внутри директории app/Services
с именем ImportService.php
Шаг 2: Настройка конструктора и методов
Откройте созданный файл app/Services/ImportService.php
и добавьте необходимые методы и зависимости.
Вот пример кода для ImportService
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | <?php namespace App\Services; use App\Models\ImportSetting; use PhpImap\Mailbox; use Webklex\IMAP\Facades\Client; class ImportService { private ImportSetting $setting; public function importFile($importSettingId) { $this->setting = ImportSetting::findOrFail($importSettingId); $this->mailDownload( $this->setting ); } private function mailDownload($setting){ $client = Client::account('default'); $client->connect(); $folder = $client->getFolderByName($setting->mailbox_folder); // Проверка, существует ли папка if (!$folder) { throw new \Exception('Папка в почте не найдена'); } $aMessage = $folder->query()->all()->get(); $aMessage->each(function($oMessage) use($setting) { $aAttachment = $oMessage->getAttachments(); $aAttachment->each(function ($oAttachment) use($setting) { /** @var \Webklex\IMAP\Attachment $oAttachment */ // Преобразуем строку в массив $lines = explode("\n", trim($oAttachment->content)); $header = str_getcsv(array_shift($lines)); // Извлекаем заголовки // Получаем полное имя класса модели из переменной $setting->importer $modelClass = $setting->importer; foreach ($lines as $line) { $row = str_getcsv($line); // Проверяем, что количество полей совпадает if (count($row) == count($header)) { $logData = array_combine($header, $row); // Динамическое создание экземпляра модели и сохранение данных $modelInstance = new $modelClass; $modelInstance->fill($logData)->save(); } } }); }); } } |
Создайте новый класс командной строки для парсинга писем и загрузки файлов. Выполните следующую команду:
1 | php artisan make:command ParseEmailsCommand |
Откроем созданный файл app\Console\Commands\ParseEmailsCommand.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | <?php namespace App\Console\Commands; use Illuminate\Console\Command; use App\Models\ImportSetting; // Импортируйте модель use App\Services\ImportService; // Импортируйте сервис class ParseEmailsCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'parse:emails {importSettingId}'; /** * The console command description. * * @var string */ protected $description = 'Импорт данных email через IMAP'; protected $importService; public function __construct(ImportService $importService) { parent::__construct(); $this->importService = $importService; } /** * Execute the console command. */ public function handle() { $importSettingId = $this->argument('importSettingId'); try { $this->importService->importFile($importSettingId); // Вызовите сервис $this->info('Данные успешно импортированы.'); } catch (\Exception $e) { $this->error('Ошибка импорта: ' . $e->getMessage()); } } } |
Интерфейс для загрузки файла
Создадим интерфейс на filament
1 | php artisan make:filament-resource ImportSetting --generate |
После создания стандартного ресурса, нам нужно будет добавить селект для выбора модели импорта.
Для этого создадим еще один сервис app\Services\OptionService.php
app\Services\OptionService.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | <?php namespace App\Services; use Illuminate\Support\Facades\File; use Illuminate\Support\Str; use ReflectionClass; class OptionService { public function listModels(): array { $importerDirectories = ['App\\Models']; $importers = []; foreach ($importerDirectories as $namespace) { // Convert the namespace to a path relative to `app/` $path = app_path(str_replace('\\', DIRECTORY_SEPARATOR, Str::after($namespace, 'App\\'))); if (!is_dir($path)) { continue; } $importers = array_merge($importers, $this->getimportersFromDirectory($namespace, $path)); } // Format class names with spaces and include subdirectory prefixes return array_map(function ($class) { return $this->formatClassWithPrefix($class); }, $importers); } protected function getimportersFromDirectory(string $namespace, string $directory): array { $importers = []; foreach (File::allFiles($directory) as $file) { $className = $namespace.'\\'.Str::replaceLast('.php', '', $file->getRelativePathname()); $className = Str::replace(DIRECTORY_SEPARATOR, '\\', $className); // Ensure the class exists and is not abstract if (class_exists($className) && !(new ReflectionClass($className))->isAbstract()) { // Remove the base namespace but keep subdirectories in the label $relativePath = Str::after($className, $namespace.'\\'); $importers[$className] = $relativePath; } } return $importers; } protected function formatClassWithPrefix(string $class): string { // Extract the namespace and class name $className = class_basename($class); $namespace = trim(Str::beforeLast($class, '\\'), '\\'); // Convert the class name to a readable format with spaces $formattedClassName = preg_replace('/(?<!^)([A-Z])/', ' $1', $className); // Extract the subdirectory prefix from the namespace, relative to `App` $prefix = Str::after($namespace, 'App\\'); // If there's no prefix (root level), return just the class name if ($prefix === $className || empty($prefix)) { return $formattedClassName; } // Otherwise, include the subdirectory prefix return "{$prefix} - {$formattedClassName}"; } } |
Теперь можно зарегистрировать этот сервис в фасаде. Для этого создадим файл app\Facades\Option.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <?php namespace App\Facades; use App\Services\OptionService; use Illuminate\Support\Facades\Facade; /** * @see \VisualBuilder\ExportScheduler\ExportScheduler * @method static bool isValidCronExpression(string $expression) * @method static array listImporters() */ class Option extends Facade { protected static function getFacadeAccessor() { return OptionService::class; } } |
Теперь можно добавить в селект выбор Модели для импорта

И форма для редактирования и создания будет выглядеть следующим образом

Запуск скрипта
Сделаем кнопку запуска через интерфейс администратора
Шаг 1. Создадим 3 файла

app\Filament\Actions\RunImportTrait.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | <?php namespace App\Filament\Actions; use Filament\Notifications\Notification; use Filament\Support\Enums\Alignment; use Illuminate\Support\Facades\Artisan; trait RunImportTrait { protected function setupRunImportAction() { $this ->icon('heroicon-s-play') ->color('success') ->modalFooterActionsAlignment(Alignment::End) ->action(function ($record) { $this->runImportAction($record); }); } protected function runImportAction($record) { $recordId = $record->id; // Вызов команды import:data с передачей ID $result = Artisan::call('parse:emails', [ 'importSettingId' => $recordId, ]); // Получение вывода команды $output = Artisan::output(); Notification::make() ->title( $record->name) ->body($output) ->success() ->send(); } } |
app\Filament\Actions\RunImport.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <?php namespace App\Filament\Actions; use Filament\Actions\Action; class RunImport extends Action { use RunImportTrait; protected function setUp():void { parent::setUp(); $this->setupRunImportAction(); } } |
app\Filament\Actions\Tables\RunImport.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?php namespace App\Filament\Actions\Tables; use App\Filament\Actions\RunImportTrait; use Filament\Tables\Actions\Action; class RunImport extends Action { use RunImportTrait; protected function setUp(): void { parent::setUp(); $this->setupRunImportAction(); } } |
Теперь можно добавить эти кнопки запуска в наш ресурс

И в форму редактирования

Теперь чтобы запустить скрипт, достаточно нажать на кнопку Run:

Если в ошибок нет, то данные файла сохранятся в нашей базе данных

В случае ошибки выйдет информационное сообщение

Вы можете настроить регулярный запуск этого скрипта с помощью планировщика задач (cron
) на вашем сервере.
Заключение
Мы рассмотрели процесс создания скрипта на PHP для автоматической загрузки файлов из электронной почты в базу данных. Этот подход позволяет существенно упростить обработку больших объемов данных и минимизировать рутинную работу. Использование современных инструментов, таких как Laravel, Filament и webklex/laravel-imap
, делает этот процесс быстрым и удобным.