NativePHP позволяет нам использовать все практические знания, которые у нас уже есть о Laravel, для создания собственных приложений для Mac, Windows и Linux.
Давайте поработаем над приложением, которое позволяет хранить часовые пояса ваших друзей, чтобы вы могли увидеть сколько у них сейчас время.
Следуйте за мной, когда мы соберем приложение Mac, чтобы узнать местное время каждого члена вашей команды.
Как работает NativePHP
NativePHP позволяет выбирать из двух различных популярных технологий для использования под капотом, Electron и Tauri. Они оба позволяют создать кроссплатформенные настольные приложения с помощью JavaScript, HTML и CSS. NativePHP предоставляет простой API для Laravel.
Установка NativePHP
Чистая установка Laravel:
laravel new team-time
Начнем с установки пакета:
composer require nativephp/electron
Запустите установщик:
php artisan native:install
Would you like to install the NativePHP NPM dependencies? - Select 'yes'
Would you like to start the NativePHP development server? - Select 'no'
Запустите приложение вручную:
php artisan native:serve
Через мгновение вы увидите, как встроенное настольное приложение отображает домашнюю страницу Laravel по умолчанию.
Перейдите к App\Providers\NativeAppServiceProvider.php. Здесь вы можете увидеть некоторые из встроенных функций PHP API, разработанных специально для вас. Однако в данном примере мы не собираемся использовать этот код. Продолжайте и очистите все в методе загрузки и замените его следующим:
namespace App\Providers;
use Native\Laravel\Facades\MenuBar;
class NativeAppServiceProvider
{
public function boot(): void
{
Menubar::create();
}
}
Поскольку NativePHP выполняет горячую перезагрузку, мы должны увидеть, как окно закроется и в верхней части вашего компьютера появится значок панели меню. Нажав на нее, вы откроете ту же домашнюю страницу Laravel по умолчанию.
Устанавите TailwindCSS, Laravel Livewire, Blade Heroicons, а затем добавляю нашу модель TeamMember, миграцию и фабрику со следующей командой:
php artisan make:model TeamMember -mf
Миграция:
public function up(): void
{
Schema::create('team_members', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('timezone');
$table->timestamps();
});
}
Фабрика:
public function definition(): array
{
return [
'name' => $this->faker->name,
'timezone' => $this->faker->random(timezone_identifiers_list())
];
}
Затем обновляю свой App\Database\seeders\DatabaseSeeder.php к:
public function run(): void
{
\App\Models\TeamMember::factory(10)->create();
}
И запускаем
php artisan migrate и php artisan db:seed
Создание классов и представления Livewire
php artisan livewire:make TeamMember/Index
php artisan livewire:make TeamMember/Create
php artisan livewire:make TeamMember/Update
Затем обновите свой web.php к следующему:
Route::get('/', \App\Livewire\TeamMember\Index::class);
Route::get('/team-members/create', \App\Livewire\TeamMember\Create::class);
Route::get('/team-members/{teamMember}/edit', \App\Livewire\TeamMember\Update::class);
И создать app.blade.php внутри resources/views/components/layouts со следующим html:
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laravel</title>
@vite('resources/css/app.css')
</head>
<body class="antialiased bg-gray-900 text-gray-100">
<div class="max-w-md mx-auto px-4 py-6">
{{$slot}}
</div>
</body>
</html>
Список наших товарищей по команде
Внутри класса App\Livewire\TeamMember\Index нам нужно получить всех членов команды, чтобы отобразить их, кроме того, мы должны предложить ссылку для создания нового члена команды и предложить кнопки обновления и удаления для существующих членов команды.
Класс:
namespace App\Livewire\TeamMember;
use App\Models\TeamMember;
use Livewire\Component;
class Index extends Component
{
public function deleteMember(TeamMember $member)
{
$member->delete();
}
public function render()
{
$team = TeamMember::get();
return view('livewire.team-member.index', compact('team'));
}
}
Представление:
<div>
<div class="flex items-center justify-between mb-10">
<h1 class="text-xl font-bold">My Team</h1>
<a href="{{route('create')}}" type="button"
class="rounded-full bg-pink-600 px-2 py-1 text-xs font-bold text-white shadow hover:bg-pink-500">Добавить товарища по команде</a>
</div>
<div wire:poll>
@foreach($team as $member)
<div wire:key="{{ $member->id }}" class="my-2 flex items-center justify-between">
<div>
<p class="text-xs font-bold text-sky-500">{{$member->name}}</p>
<p class="text-lg">{{now()->tz($member->timezone)->format('h:i:s A')}} <span
class="text-xs text-gray-500">- {{$member->timezone}}</span></p>
</div>
<div class="flex items-center">
<a href="{{route('edit', ['team-member' => $member])}}">
<span class="sr-only">Редактировать</span>
<x-heroicon-m-pencil class="w-5 h-5 mr-3 hover:text-pink-500 transition-all duration-300" />
</a>
<button wire:click="deleteMember({{$member}})">
<x-heroicon-m-trash class="w-5 h-5 mr-3 hover:text-red-600 transition-all duration-300" />
</button>
</div>
</div>
@endforeach
</div>
</div>
NativePHP использует локальную базу данных SQLite за кулисами, нам не нужна дополнительная настройка или настройка для нее.
Теперь давайте обработаем операции Create, чтобы мы могли увидеть это и в нативном приложении.
Класс:
namespace App\Livewire\TeamMember;
use App\Models\TeamMember;
use Livewire\Attributes\Rule;
use Livewire\Component;
class Create extends Component
{
#[Rule(['required', 'string', 'min:3'])]
public string $name;
#[Rule(['required', 'string', 'min:3'])]
public string $timezone;
public function createMember()
{
TeamMember::create($this->validate());
$this->redirectRoute('index');
}
public function render()
{
return view('livewire.team-member.create');
}
}
Представление:
<div>
<div class="flex items-center justify-between mb-10">
<h1 class="text-xl font-bold">Добавить члена команды</h1>
<a href="{{route('index')}}" type="button" class="rounded-full bg-pink-600 px-2 py-1 text-xs font-bold text-white shadow hover:bg-pink-500 flex items-center">Назад</a>
</div>
<form wire:submit="createMember">
<div>
<label for="name" class="block text-sm font-medium leading-6 text-gray-100">Как зовут члена вашей команды?</label>
<div class="mt-2">
<input type="text" wire:model="name" id="name" class="block w-full rounded-md border-0 py-1.5 text-gray-400 shadow-sm bg-gray-800 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-pink-600 sm:text-sm sm:leading-6" placeholder="Павел">
@error('name')
<div class="mt-1 text-red-500 text-sm">{{ $message }}</div>
@enderror
</div>
</div>
<div class="mt-6">
<label for="timezone" class="block text-sm font-medium leading-6 text-gray-100">Какой часовой пояс у члена вашей команды</label>
<select id="timezone" wire:model="timezone" class="mt-2 block w-full rounded-md border-0 py-1.5 text-gray-400 shadow-sm bg-gray-800 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-pink-600 sm:text-sm sm:leading-6">
@foreach(timezone_identifiers_list() as $timezone)
<option wire:key="{{ $timezone }}">{{$timezone}}</option>
@endforeach
</select>
@error('timezone')
<div class="mt-1 text-red-500 text-sm">{{ $message }}</div>
@enderror
</div>
<button type="submit" class="mt-6 rounded bg-pink-600 px-2 py-1 font-bold text-white shadow hover:bg-pink-500 w-full">Добавить товарища по команде</button>
</form>
</div>
Давайте настроим наш класс редактирования.
Класс:
namespace App\Livewire\TeamMember;
use App\Models\TeamMember;
use Livewire\Component;
use Livewire\Features\SupportValidation\Rule;
class Update extends Component
{
public TeamMember $teamMember;
#[Rule(['required','min:3', 'string'])]
public $name;
#[Rule(['required','string'])]
public $timezone;
public function mount(TeamMember $teamMember)
{
$this->teamMember = $teamMember;
$this->name = $teamMember->name;
$this->timezone = $teamMember->timezone;
}
public function saveMember()
{
$this->teamMember->update([
'name' => $this->name,
'timezone' => $this->timezone
]);
$this->redirectRoute('index');
}
public function render()
{
return view('livewire.team-member.update');
}
}
Представление:
<div>
<div class="flex items-center justify-between mb-10">
<h1 class="text-xl font-bold">Обновить члена команды</h1>
<a href="{{route('index')}}" type="button" class="rounded-full bg-pink-600 px-2 py-1 text-xs font-bold text-white shadow hover:bg-pink-500 flex items-center">Назад</a>
</div>
<form wire:submit="saveMember">
<div>
<label for="name" class="block text-sm font-medium leading-6 text-gray-100">Имя</label>
<div class="mt-2">
<input type="text" wire:model.blur="name" id="name" class="block w-full rounded-md border-0 py-1.5 text-gray-200 shadow-sm bg-gray-800 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-pink-600 sm:text-sm sm:leading-6" placeholder="Павел">
@error('name')
<div class="mt-1 text-red-500 text-sm">{{ $message }}</div>
@enderror
</div>
</div>
<div class="mt-6">
<label for="timezone" class="block text-sm font-medium leading-6 text-gray-100">Часовой пояс</label>
<select id="timezone" wire:model="timezone" class="mt-2 block w-full rounded-md border-0 py-1.5 text-gray-200 shadow-sm bg-gray-800 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-pink-600 sm:text-sm sm:leading-6">
@foreach(timezone_identifiers_list() as $timezone)
<option {{$teamMember->timezone === $timezone ? 'selected' : ''}}>{{$timezone}}</option>
@endforeach
</select>
@error('timezone')
<div class="mt-1 text-red-500 text-sm">{{ $message }}</div>
@enderror
</div>
<button type="submit" class="mt-6 rounded bg-pink-600 px-2 py-1 font-bold text-white shadow hover:bg-pink-500 w-full">Добавить товарища по команде</button>
</form>
</div>
Теперь, когда приложение работает и выглядит так, как мы хотим, давайте сделаем еще пару вещей, прежде чем создавать его. Во-первых, давайте обновим значки в строке меню. Я создал 2 изображения, одно в формате 22x22 png, а другое в формате 44x44 png. На Mac NativePHP преобразует эти изображения в белый значок с прозрачностью, чтобы он соответствовал цветовой гамме встроенной строки меню.
Путем добавления этих значков в каталог storage/app, а затем обновления NativeAppServiceProvider до:
public function boot(): void
{
Menubar::create()->icon(storage_path('app/menuBarIconTemplate.png'));;
}
Наконец, давайте добавим некоторые элементы в наш файл .env, чтобы сообщить NativePHP некоторые подробности о нашем приложении:
NATIVEPHP_APP_NAME="TeamTime"
NATIVEPHP_APP_VERSION="1.0.0"
NATIVEPHP_APP_ID="com.teamtime.desktop"
NATIVEPHP_DEEPLINK_SCHEME="teamtime"
NATIVEPHP_APP_AUTHOR="Topsite Web"
NATIVEPHP_UPDATER_ENABLED=false
Создайте свое приложение NativePHP
php artisan native:build
Выполнение этой команды упакует все, что нам нужно для локальной сборки приложения, и предоставит нам собственный файл ('.dmg', '.exe' и т.д.). После завершения файлы будут помещены в root/dist, и вы сможете распространять приложение по своему усмотрению.
Заключение
Довольно круто, что мы можем создавать нативные приложения с помощью Laravel. Я могу придумать много сценариев использования такой функции, и мне не терпится продолжить изучение и увидеть, как Ларавел подталкивается на новые высоты.