• Czas czytania ~12 min
  • 04.08.2023

NativePHP, stworzony przez Marcela Pociota w BeyondCode, pozwala nam programistom Laravel wykorzystać CAŁĄ praktyczną wiedzę, którą już mamy z Laravel, do tworzenia natywnych aplikacji Mac, Windows i Linux.

Niedawno widziałem Christophera Rumpela pracującego nad aplikacją, która pozwala przechowywać strefy czasowe znajomych, dzięki czemu można zobaczyć, która jest godzina na pierwszy rzut oka.

Śledź mnie, gdy tworzymy aplikację Mac MenuBar, aby poznać czas lokalny każdego członka zespołu.

Czekaj - jak NativePHP w ogóle działa??

NativePHP pozwala wybierać spośród dwóch różnych popularnych technologii do użycia pod maską, Electron i Tauri. Oba pozwalają "tworzyć wieloplatformowe aplikacje klasyczne za pomocą JavaScript, HTML i CSS". To trochę jak czary, jeśli się nad tym zastanowić - technologie internetowe do budowania "natywnych" aplikacji. NativePHP zapewnia prosty interfejs API ze znanym (Laravel) sposobem budowania aplikacji w jednej z tych podstawowych technologii. W tym przykładzie zademonstruję opakowanie Electron.

Instalacja NativePHP i Hello World

W świeżej aplikacji Laravel:Zacznijmy od instalacji pakietu:Uruchom instalator:Chcę, abyś ręcznie uruchomił aplikację, abyś przyzwyczaił się do robienia tego w ten sposób:

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

Po chwili powinieneś zobaczyć natywną aplikację komputerową wyświetlającą domyślną stronę główną Laravel, cześć!

Pokaż kod!

Jasne, ale uspokój się trochę, wszystko zostanie ujawnione wkrótce. Przejdź do App\Providers\NativeAppServiceProvider.phpfolderu . Tutaj możesz zobaczyć niektóre z NativePHP API stubed dla Ciebie. W tym przykładzie nie będziemy jednak używać tego kodu. Śmiało i wyczyść wszystko w metodzie boot i zastąp ją następującą: Ponieważ NativePHP wykonuje przeładowanie na gorąco,

<?php
namespace App\Providers;

use Native\Laravel\Facades\MenuBar;

class NativeAppServiceProvider
{
    public function boot(): void
    {
        Menubar::create();
    }
}

powinniśmy zobaczyć zamknięcie Window i Menubar ikonę pojawić się u góry komputera. Kliknięcie na niego spowoduje wyświetlenie tej samej domyślnej strony głównej Laravel.

Miły! Zbudujmy coś fajnego!

Za kulisami instaluję TailwindCSS zgodnie z ich dokumentacją, Laravel Livewire 3 (dicey, wiem, ale to mój narkotyk z wyboru), Blade Heroicons, a następnie dodając nasz model TeamMember, migrację i fabrykę za pomocą następującego polecenia: Następnie aktualizuję moje App\Database\seeders\DatabaseSeeder.php to:

php artisan make:model TeamMember -mf
NOTE: I am keeping `npm run dev` running for hot reloading of the ui.

Następnie aktualizuję moje App\Database\seeders\DatabaseSeeder.php to:

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->randomElement(timezone_identifiers_list())
    ];
}

public function run(): void
{
     \App\Models\TeamMember::factory(10)->create();
}

And działa php artisan migrate i .

NOTE: The application inside of NativePHP does NOT have access to the database defined in your `.env`. From my experience, it can be useful to seed your database locally and debug in the browser or by using Spatie/Ray.

php artisan db:seedStwórzmy nasze klasy i widoki

php artisan livewire:make TeamMember/Index
php artisan livewire:make TeamMember/Create
php artisan livewire:make TeamMember/Update

Livewire Następnie zaktualizuj nasze web.php do następujących: I utwórz wnętrze app.blade.php resources/views/components/layouts za pomocą następującego kodu html:

Route::get('/', \App\Livewire\TeamMember\Index::class)->name('index');
Route::get('/team-members/create', \App\Livewire\TeamMember\Create::class)->name('create');
Route::get('/team-members/{teamMember}/edit', \App\Livewire\TeamMember\Update::class)->name('edit');

<!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>

Lista naszych członków zespołu Wewnątrz App\Livewire\TeamMember\Index klasy musimy pobrać wszystkich członków zespołu, aby je wyświetlić, dodatkowo powinniśmy zaoferować link do utworzenia nowego członka zespołu i zaoferować przyciski aktualizacji i usunięcia dla istniejących członków

zespołu.

Klasa:Widok:Jeśli baza danych została udostępniona lokalnie, podgląd w przeglądarce powinien wyglądać tak:

<?php
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">Add Team
            Mate</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">Edit</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>

W aplikacji natywnej powinno to wyglądać tak, ponieważ nie mamy tam jeszcze żadnych danych (upewnij się,

że wtedy php artisan native:serveuruchomiłeśnpm run build). NativePHP używa lokalnej bazy danych SQLite za kulisami, nie potrzebujemy do tego żadnej dodatkowej konfiguracji ani konfiguracji.

Teraz zajmijmy się operacjamiCreate, abyśmy mogli to zobaczyć również w natywnej aplikacji.

Klasa:Widok:Jeśli baza danych została udostępniona lokalnie, podgląd w przeglądarce powinien wyglądać tak:

<?php
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">Add Team Member</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">
            Go Back
        </a>
    </div>
    <form wire:submit="createMember">
        <div>
            <label for="name" class="block text-sm font-medium leading-6 text-gray-100">What is your team member's
                name?</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="Sarthak">
                @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">What is your team member's
                timezone</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">Add Team
            Mate
        </button>
    </form>
</div>

Teraz gotujemy'! Ale wygląda na to, że ustawiłem Sarthak na złą strefę czasową, ustawmy naszą klasę edycji i widok i uśpijmy tego szczeniaka.

Klasa:Widok:Jeśli baza danych została udostępniona lokalnie, podgląd w przeglądarce powinien wyglądać tak:

<?php
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">Update Team Member</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">
            Go Back
        </a>
    </div>
    <form wire:submit="saveMember">
        <div>
            <label for="name" class="block text-sm font-medium leading-6 text-gray-100">Name</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="Sarthak">
                @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">Timezone</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">Add Team
            Mate
        </button>
    </form>
</div>

Wrapping It Up

Teraz, gdy aplikacja działa i wygląda tak, jak chcemy, zróbmy jeszcze kilka rzeczy, zanim ją zbudujemy. Najpierw zaktualizujmy ikony paska menu. Stworzyłem 2 obrazy, jeden to png 22x22, a drugi to png 44x44. Umieszczając nazwę tych plików słowem Template uzyskujemy jakąś fajną funkcjonalność. Na komputerze Mac NativePHP przekonwertuje te obrazy na białą ikonę z przezroczystością, tak aby pasowała do schematu kolorów natywnego paska menu.

Dwa obrazy są nazwane:Dodając te ikony do katalogu, a następnie aktualizując naszą NativeAppServiceProvider metodę rozruchu dostorage/app:

menuBarIconTemplate.png
[email protected]

public function boot(): void
{
    Menubar::create()->icon(storage_path('app/menuBarIconTemplate.png'));;
}

Przy następnym serwisie powinniśmy zobaczyć aktualizację ikony na naszym pasku menu.

Na koniec dodajmy kilka elementów do naszego .env pliku, aby przekazać NativePHP kilka szczegółów na temat naszej aplikacji:

NATIVEPHP_APP_NAME="TeamTime"
NATIVEPHP_APP_VERSION="1.0.0"
NATIVEPHP_APP_ID="com.teamtime.desktop"
NATIVEPHP_DEEPLINK_SCHEME="teamtime"
NATIVEPHP_APP_AUTHOR="Shane D Rosenthal"
NATIVEPHP_UPDATER_ENABLED=false

Zbuduj swoją aplikację

php artisan native:build

NativePHP Uruchomienie tego polecenia spakuje wszystko, czego potrzebujemy do zbudowania aplikacji lokalnie i da nam natywny plik (".dmg", ".exe" itp.). Po zakończeniu pliki zostaną umieszczone w katalogu projektu root/dist i możesz rozpowszechniać aplikację według własnego uznania.

W chwili pisania tego tekstu działa funkcja php artisan native:build, jednak kiedy otwieram .dmg lokalnie, "zawiesza się", a moja aplikacja paska menu nie uruchamia się. Ponownie, NativePHP jest nadal w stanie i spodziewane są problemy, zespół BeyondCode ciężko pracuje nad naprawieniem takich elementów i powinniśmy spodziewać się pełnej funkcjonalności w alpha nadchodzących tygodniach lub miesiącach.

Podsumowanie

Cóż, co o tym sądzisz? Niesamowite, że możemy tworzyć natywne aplikacje za pomocą Laravel, prawda? Mogę wymyślić wiele przypadków użycia takiej funkcji i nie mogę się doczekać, aby kontynuować eksplorację i zobaczyć, jak Laravel zostaje popchnięty na nowe wyżyny. W dokumentach NativePHP jest tak wiele innych elementów, że ta aplikacja nie obejmuje ani nie przechodzi, spójrz sam, zainspiruj się i zbuduj coś niesamowitego. #laravelforever!

Comments

No comments yet
Yurij Finiv

Yurij Finiv

Full stack

O

Professional Fullstack Developer with extensive experience in website and desktop application development. Proficient in a wide range of tools and technologies, including Bootstrap, Tailwind, HTML5, CSS3, PUG, JavaScript, Alpine.js, jQuery, PHP, MODX, and Node.js. Skilled in website development using Symfony, MODX, and Laravel. Experience: Contributed to the development and translation of MODX3 i...

O autorze CrazyBoy49z
WORK EXPERIENCE
Kontakt
Ukraine, Lutsk
+380979856297