• Czas czytania ~12 min
  • 15.06.2022

Wszyscy tam byliśmy, chcemy zintegrować się z zewnętrznym API w Laravel i zadajemy sobie pytanie „Jak mam to zrobić?”. Jeśli chodzi o integracje API, nie jestem obcy, ale wciąż za każdym razem zastanawiam się, jaki będzie najlepszy sposób. Sam Carré zbudował pakiet na początku 2022 roku Salon, które mogą sprawić, że nasze integracje API będą niesamowite. Jednak ten artykuł będzie zupełnie inny, będzie to przewodnik po tym, jak można go użyć do zbudowania integracji od podstaw.

Podobnie jak wszystkie wspaniałe rzeczy, zaczyna się od laravel new i zaczyna się stamtąd, więc zacznijmy.Teraz, jeśli chodzi o instalację Laravela, możesz użyć instalatora lub kompozytora laravela - ta część zależy od Ciebie. Poleciłbym jednak instalator, jeśli możesz, ponieważ zapewnia on łatwe opcje do zrobienia czegoś więcej niż tylko stworzenia projektu. Utwórz nowy projekt i otwórz go w wybranym przez siebie edytorze kodu. Gdy już tam dotrzemy, możemy zacząć.

Co zamierzamy zbudować? Cieszę się, że zapytałeś!Zamierzamy zbudować integrację z interfejsem API GitHub, aby uzyskać listę przepływów pracy dostępnych dla repozytorium. Może to być bardzo pomocne, jeśli tak jak ja spędzasz dużo czasu w wierszu poleceń. Pracujesz nad aplikacją, wprowadzasz zmiany do oddziału lub tworzysz PR - przechodzi to przez przepływ pracy, który może uruchamiać jedną z wielu innych rzeczy.Znajomość statusu tego przepływu pracy ma czasami ogromny wpływ na to, co robisz dalej. Czy ta funkcja jest kompletna? Czy wystąpiły problemy z naszym przebiegiem pracy? Czy nasze testy lub analiza statyczna kończą się pomyślnie? Wszystkie te rzeczy zwykle czekasz i sprawdzasz repozytorium na GitHub, aby zobaczyć status.Ta integracja pozwoli Ci uruchomić polecenie rzemieślnika, uzyskać listę dostępnych przepływów pracy dla repozytorium i wyzwolić nowe uruchomienie przepływu pracy.

Więc do tej pory kompozytor powinien był zrobić swoje i zainstalować idealny punkt wyjścia, aplikację Laravel.Następnie musimy zainstalować Saloon - ale chcemy się upewnić, że zainstalujemy wersję laravel, więc uruchom następujące polecenie w swoim terminalu:

composer require sammyjo20/saloon-laravel

Po prostu jesteśmy już o krok bliżej do łatwiejszych integracji. Jeśli masz jakiekolwiek problemy na tym etapie, upewnij się, że sprawdziłeś obie wersje Laravel i PHP, których używasz, ponieważ Saloon wymaga co najmniej Laravel 8 i PHP 8!

Więc teraz, gdy mamy zainstalowany Saloon, musimy utworzyć nową klasę. W terminologii Saloons są to „złącza”, a wszystko, co robi złącze, to tworzenie sposobu skoncentrowanego na obiektach - to API jest połączone przez tę klasę.Istnieje przydatne polecenie rzemieślnika, które pozwala je tworzyć, więc uruchom następujące polecenie rzemieślnika, aby utworzyć łącznik GitHub:

php artisan saloon:connector GitHub GitHubConnector

To polecenie jest podzielone na 2 części, pierwszym argumentem jest integracja, którą tworzysz, a drugim jest nazwa konektora, który chcesz utworzyć.Oznacza to, że możesz utworzyć wiele łączników do integracji - co daje dużą kontrolę nad łączeniem się na wiele różnych sposobów, jeśli zajdzie taka potrzeba.

Stworzy to dla Ciebie nową klasę w app/Http/Integrations/GitHub/GitHubConnector.php, przyjrzyjmy się temu przez chwilę i zrozum, co się dzieje.

Pierwszą rzeczą, jaką widzimy, jest to, że nasz łącznik rozszerza SaloonConnector, co pozwoli nam na uruchomienie naszego łącznika bez dużej ilości kodu wzorcowego. Następnie dziedziczymy cechę o nazwie AcceptsJson. Teraz, jeśli spojrzymy na dokumentację Saloon, wiemy, że jest to wtyczka.To po prostu dodaje nagłówek do naszych żądań, informujący zewnętrzny interfejs API, że chcemy akceptować odpowiedzi JSON.Następną rzeczą, którą widzimy, jest to, że mamy metodę definiowania podstawowego adresu URL dla naszego łącznika - dodajmy więc nasz w:

public function defineBaseUrl(): string
{
    return 'https://api.github.com';
}

Ładne i czyste, moglibyśmy nawet posunąć się trochę dalej, więc mamy do czynienia z mniej luźnymi sznurkami wiszącymi w naszej aplikacji - więc spójrzmy, jak możemy to zrobić. Wewnątrz twojego config/services.plik php dodaj nowy rekord usługi:

'github' => [
    'url' => env('GITHUB_API_URL', 'https://api.github.com'),
]

To, co to zrobi, to umożliwi nam obejście tego w różnych środowiskach - dając nam lepsze i bardziej testowalne rozwiązanie. Lokalnie moglibyśmy nawet kpić z interfejsu API GitHub przy użyciu ich specyfikacji OpenAPI i przetestować go, aby upewnić się, że działa. Jednak ten samouczek dotyczy Saloon, więc robię dygresję...Teraz zrefaktoryzujmy naszą podstawową metodę adresu URL, aby użyć konfiguracji:

public function defineBaseUrl(): string
{
    return (string) config('services.github.url');
}

Jak widać, teraz pobieramy nowo dodany rekord z naszej konfiguracji — i rzutujemy go na ciąg znaków w celu zapewnienia bezpieczeństwa typów — config() zwraca mieszany wynik, więc chcemy być surowi to jeśli możemy.

Następnie mamy domyślne nagłówki i domyślną konfigurację, teraz w tej chwili nie będę się martwić domyślnymi nagłówkami, ponieważ za chwilę podejdziemy do samodzielnego auth. Ale konfiguracja to miejsce, w którym możemy zdefiniować opcje przełykania dla naszej integracji, ponieważ Saloon używa Guzzle pod maską.Na razie ustawmy limit czasu i przejdźmy dalej, ale poświęć trochę czasu na konfigurację według własnego uznania:

public function defaultConfig(): array
{
    return [
        'timeout' => 30,
    ];
}

Mamy teraz nasz łącznik tak skonfigurowany, jak na razie go potrzebujemy, możemy wrócić później, jeśli znajdziemy coś, co musimy dodać. Następnym krokiem jest zastanowienie się nad prośbami, które chcemy wysłać.Jeśli spojrzymy na dokumentację API dla GitHub Actions API, mamy wiele opcji, zaczniemy od wyszczególnienia przepływów pracy dla konkretnego repozytorium: /repos/{owner}/{repo}/actions/workflows. Uruchom następujące polecenie rzemieślnika, aby utworzyć nowe żądanie:

php artisan saloon:request GitHub ListRepositoryWorkflowsRequest

Ponownie pierwszym argumentem jest integracja, a drugim argumentem jest nazwa żądania, które chcemy utworzyć.Musimy upewnić się, że nazwaliśmy integrację dla tworzonego przez nas żądania, aby znajdowała się we właściwym miejscu, a następnie musimy nadać jej nazwę. Nazwałem moje ListRepositoryWorkflowsRequest, ponieważ lubię opisowe podejście do nazewnictwa - jednak możesz dostosować to do tego, jak lubisz nazywać rzeczy, ponieważ nie ma tu naprawdę złego sposobu.Spowoduje to utworzenie nowego pliku do obejrzenia: app/Http/Integrations/GitHub/Requests/ListRepositoryWorkflowsRequest.php — przyjrzyjmy się temu teraz.

Ponownie rozszerzamy tutaj klasę biblioteki, tym razem SaloonRequest, czego należy się spodziewać. Następnie mamy właściwość łącznika i metodę.Możemy zmienić metodę, jeśli zajdzie taka potrzeba - ale domyślny GET jest tym, czego teraz potrzebujemy. Następnie mamy metodę definiowania punktu końcowego. Refaktoryzuj swoją klasę żądania, aby wyglądała jak w poniższym przykładzie:

class ListRepositoryWorkflowsRequest extends SaloonRequest
{
    protected ?string $connector = GitHubConnector::class;
 
    protected ?string $method = Saloon::GET;
 
    public function __construct(
        public string $owner,
        public string $repo,
    ) {}
 
    public function defineEndpoint(): string
    {
        return "/repos/{$this->owner}/{$this->repo}/actions/workflows";
    }
}

To, co zrobiliśmy, to dodanie konstruktora, który akceptuje repozytorium i właściciela jako argumenty, których możemy następnie użyć w ramach naszej metody definiowania punktu końcowego.Ustawiliśmy również łącznik na GitHubConnector, który utworzyliśmy wcześniej. Mamy więc prośbę, którą możemy wysłać, możemy zrobić mały krok od integracji i zamiast tego pomyśleć o poleceniu konsoli.

Jeśli nie utworzyłeś wcześniej polecenia konsoli w Laravel, sprawdź dokumentacja jest bardzo dobra. Uruchom następujące polecenie rzemieślnika, aby utworzyć pierwsze polecenie dla tej integracji:

php artisan make:command GitHub/ListRepositoryWorkflows

Spowoduje to utworzenie następującego pliku: app/Console/Commands/GitHub/ListRespositoryWorkflows.php. Możemy teraz rozpocząć pracę z naszym poleceniem, aby wysłać żądanie i uzyskać dane, na których nam zależy.

Po uzyskaniu tokena dostępu musimy dodać go do naszego pliku .env i upewnić się, że możemy go przeciągnąć przez naszą konfigurację.

Możemy teraz rozszerzyć naszą usługę w config/services.php pod githubem, aby dodać ten token:

GITHUB_API_TOKEN=ghp_loads-of-letters-and-numbers-here

Teraz mamy dobry sposób na załadowanie tego tokena, możemy wrócić do naszego polecenia konsoli!Musimy poprawić nasz podpis, aby umożliwić nam zaakceptowanie właściciela i repozytorium jako argumentów:

'github' => [
    'url' => env('GITHUB_API_URL', 'https://api.github.com'),
    'token' => env('GITHUB_API_TOKEN'),
]

Teraz możemy skupić się na metodzie uchwytu:

class ListRepositoryWorkflows extends Command
{
    protected $signature = 'github:workflows
        {owner : The owner or organisation.}
		{repo : The repository we are looking at.}
	';
 
    protected $description = 'Fetch a list of workflows from GitHub by the repository name.';
 
    public function handle(): int
    {
        return 0;
    }
}

Tutaj zaczynamy budować nasze żądanie, przekazując argumenty bezpośrednio do samego żądania, jednak możemy chcieć stworzyć kilka zmiennych lokalnych, aby dostarczyć pewne informacje zwrotne na temat konsoli:

public function handle(): int
{
    $request = new ListRepositoryWorkflowsRequest(
        owner: $this->argument('owner'),
        repo: $this->argument('repo'),
    );
 
    return self::SUCCESS;
}

Więc mamy kilka opinii doużytkownika, co jest zawsze ważne, jeśli chodzi o komendę konsoli. Teraz musimy dodać nasz token uwierzytelniający i faktycznie wysłać żądanie:

public function handle(): int
{
    $owner = (string) $this->argument('owner');
    $repo = (string) $this->argument('repo');
 
    $request = new ListRepositoryWorkflowsRequest(
        owner: $owner,
        repo: $repo,
    );
 
    $this->info(
        string: "Fetching workflows for {$owner}/{$repo}",
    );
 
    return self::SUCCESS;
}

Jeśli zmienisz powyższe i wykonasz dd() na $response->json(), tylko na razie. Następnie uruchom polecenie:

public function handle(): int
{
    $owner = (string) $this->argument('owner');
    $repo = (string) $this->argument('repo');
 
    $request = new ListRepositoryWorkflowsRequest(
        owner: $owner,
        repo: $repo,
    );
 
    $request->withTokenAuth(
        token: (string) config('services.github.token'),
    );
 
    $this->info(
        string: "Fetching workflows for {$owner}/{$repo}",
    );
 
    $response = $request->send();
 
    return self::SUCCESS;
}

To otrzyma listę przepływów pracy dla repozytorium laravel/laravel.Nasze polecenie pozwoli ci pracować z dowolnymi publicznymi repozytoriami, jeśli chcesz, aby było to bardziej szczegółowe, możesz stworzyć listę opcji repozytoriów, które chcesz sprawdzić, zamiast akceptować argumenty - ale ta część zależy od Ciebie. W tym samouczku skupię się na szerszym, bardziej otwartym przypadku użycia.

php artisan github:workflows laravel laravel

Teraz odpowiedź, którą otrzymujemy z interfejsu API GitHub, jest świetna i pouczająca, ale będzie wymagała przekształcenia w celu wyświetlenia, a jeśli spojrzymy na nią osobno, nie ma kontekstu. Zamiast tego dodamy do naszego żądania kolejną wtyczkę, która pozwoli nam przekształcić odpowiedzi w DTO (Domain Transfer Objects), co jest świetnym sposobem na poradzenie sobie z tym.Pozwoli nam to stracić elastyczną tablicę, do której jesteśmy przyzwyczajeni pozyskiwać z interfejsów API, i uzyskać coś, co jest bardziej świadome kontekstowo. Stwórzmy DTO dla Workflow, stwórzmy nowy plik: app/Http/Integrations/GitHub/DataObjects/Workflow.php i dodaj do niego następujący kod:

Mamy konstruktor, który zawiera ważne części naszego przepływu pracy, które chcemy wyświetlić, metodę fromSaloon, która przekształci tablicę z odpowiedzi saloon w nowe DTO oraz metodę do tablicy do wyświetlania DTO z powrotem do tablicy, kiedy tego potrzebujemy.Wewnątrz naszego ListRepositoryWorkflowsRequest musimy odziedziczyć nową cechę i dodać nową metodę:

class Workflow
{
    public function __construct(
        public int $id,
        public string $name,
        public string $state,
    ) {}
 
    public static function fromSaloon(array $workflow): static
    {
        return new static(
            id: intval(data_get($workflow, 'id')),
            name: strval(data_get($workflow, 'name')),
            state: strval(data_get($workflow, 'state')),
        );
    }
 
    public function toArray(): array
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'state' => $this->state,
        ];
    }
}

Odziedziczyliśmy cechę CastsToDto, która umożliwia temu żądaniu wywołanie metody dto w odpowiedzi, a następnie dodajemy metodę castToDto gdzie możemy kontrolować, jak to się zmienia.Chcemy, aby zwracał nową kolekcję, ponieważ istnieje więcej niż jeden przepływ pracy, korzystając z przepływów pracy części treści odpowiedzi. Następnie mapujemy każdy element w kolekcji i przekształcamy go w DTO. Teraz możemy to zrobić w ten sposób lub możemy to zrobić w ten sposób, budując naszą kolekcję za pomocą DTO:

class ListRepositoryWorkflowsRequest extends SaloonRequest
{
    use CastsToDto;
 
    protected ?string $connector = GitHubConnector::class;
 
    protected ?string $method = Saloon::GET;
 
    public function __construct(
        public string $owner,
        public string $repo,
    ) {}
 
    public function defineEndpoint(): string
    {
        return "/repos/{$this->owner}/{$this->repo}/actions/workflows";
    }
 
    protected function castToDto(SaloonResponse $response): Collection
    {
        return (new Collection(
            items: $response->json('workflows'),
        ))->map(function ($workflow): Workflow {
            return Workflow::fromSaloon(
                workflow: $workflow,
            );
        });
    }
}

Tutaj możesz wybrać, co najbardziej Ci odpowiada.Osobiście wolę pierwsze podejście, ponieważ lubię przejść i zobaczyć logikę, ale nie ma nic złego w każdym podejściu - wybór należy do Ciebie.Wracając do polecenia, teraz musimy pomyśleć o tym, jak chcemy wyświetlać te informacje:

protected function castToDto(SaloonResponse $response): Collection
{
	return new Collection(
		items: $response->collect('workflows')->map(fn ($workflow) =>
			Workflow::fromSaloon(
				workflow: $workflow
			),
		)
	);
}

Więc tworzymy tabelę z nagłówkami, a następnie dla wierszy, dla których chcemy uzyskać odpowiedź DTO, i mapujemy zwróconą kolekcję, odrzucając każdy DTO z powrotem do tablicy, która ma być wyświetlona.Rzutowanie z tablicy odpowiedzi do DTO i z powrotem do tablicy może wydawać się sprzeczne z intuicją, ale spowoduje to wymuszenie typów, tak aby identyfikator, nazwa i status były zawsze tam, gdzie oczekiwano i nie da to żadnych zabawnych wyników .Pozwala na spójność tam, gdzie normalna tablica odpowiedzi może jej nie mieć, a gdybyśmy chcieli, moglibyśmy przekształcić to w obiekt wartości, do którego zamiast tego mamy dołączone zachowanie.Jeśli teraz uruchomimy nasze polecenie, powinniśmy teraz zobaczyć ładne dane wyjściowe tabeli, które są łatwiejsze do odczytania niż kilka wierszy ciągów:

public function handle(): int
{
    $owner = (string) $this->argument('owner');
    $repo = (string) $this->argument('repo');
 
    $request = new ListRepositoryWorkflowsRequest(
        owner: $owner,
        repo: $repo,
    );
 
    $request->withTokenAuth(
        token: (string) config('services.github.token'),
    );
 
    $this->info(
        string: "Fetching workflows for {$owner}/{$repo}",
    );
 
    $response = $request->send();
 
    if ($response->failed()) {
        throw $response->toException();
	}
 
    $this->table(
        headers: ['ID', 'Name', 'State'],
        rows: $response
			->dto()
			->map(fn (Workflow $workflow) =>
				  $workflow->toArray()
			)->toArray(),
    );
 
    return self::SUCCESS;
}

Na koniec samo wymienienie tych przepływów pracy jest świetne – ale pójdźmy o krok dalej w imię nauki. Załóżmy, że uruchomiłeś to polecenie w jednym ze swoich repozytoriów i chcesz ręcznie uruchomić dziennik zmian aktualizacji?A może chciałeś, aby było to uruchamiane na cronie za pomocą serwera produkcyjnego na żywo lub dowolnego zdarzenia, o którym możesz pomyśleć? Moglibyśmy ustawić dziennik zmian tak, aby uruchamiał się raz dziennie o północy, abyśmy otrzymywali codzienne podsumowania w dzienniku zmian lub cokolwiek, czego moglibyśmy chcieć.Utwórzmy kolejne polecenie konsoli, aby utworzyć nowe zdarzenie wysyłki przepływu pracy:

php artisan github:workflows laravel laravel
Fetching workflows for laravel/laravel
+----------+------------------+--------+
| ID       | Name             | State  |
+----------+------------------+--------+
| 12345678 | pull requests    | active |
| 87654321 | Tests            | active |
| 18273645 | update changelog | active |
+----------+------------------+--------+

Wewnątrz tego nowego pliku app/Http/Integrations/GitHub/Requests/CreateWorkflowDispatchEventRequest.php dodaj następujący kod, abyśmy mogli przez niego przejść:

php artisan saloon:request GitHub CreateWorkflowDispatchEventRequest

Konfigurujemy łącznik i dziedziczymy cechę HasJsonBody, aby umożliwić nam wysyłanie danych.Metoda została ustawiona jako żądanie POST, ponieważ chcemy wysłać dane. Następnie mamy konstruktor, który akceptuje części adresu URL, które budują punkt końcowy. Wreszcie mamy domyślne dane kopuły w defaultData, których możemy użyć do ustawienia wartości domyślnych dla tego żądania wiadomości.

class CreateWorkflowDispatchEventRequest extends SaloonRequest
{
    use HasJsonBody;
 
    protected ?string $connector = GitHubConnector::class;
 
    public function defaultData(): array
    {
        return [
            'ref' => 'main',
        ];
    }
 
    protected ?string $method = Saloon::POST;
 
    public function __construct(
        public string $owner,
        public string $repo,
        public string $workflow,
    ) {}
 
    public function defineEndpoint(): string
    {
        return "/repos/{$this->owner}/{$this->repo}/actions/workflows/{$this->workflow}/dispatches";
    }
}

Jak powiedziałem na początku tego samouczka, istnieje wiele sposobów podejścia do integracji API, ale jedno jest pewne — używanie Saloon sprawia, że ​​jest ono czyste i łatwe, ale także przyjemne w użyciu.

php artisan make:command GitHub/CreateWorkflowDispatchEvent
class CreateWorkflowDispatchEvent extends Command
{
    protected $signature = 'github:dispatch
        {owner : The owner or organisation.}
		{repo : The repository we are looking at.}
		{workflow : The ID of the workflow we want to dispatch.}
		{branch? : Optional: The branch name to run the workflow against.}
	';
 
    protected $description = 'Create a new workflow dispatch event for a repository.';
 
    public function handle(): int
    {
        $owner = (string) $this->argument('owner');
        $repo = (string) $this->argument('repo');
        $workflow = (string) $this->argument('workflow');
 
        $request = new CreateWorkflowDispatchEventRequest(
            owner: $owner,
            repo: $repo,
            workflow: $workflow,
        );
 
        $request->withTokenAuth(
            token: (string) config('services.github.token'),
        );
 
		if ($this->hasArgument('branch')) {
			$request->setData(
				data: ['ref' => $this->argument('branch')],
			);
        }
 
        $this->info(
            string: "Requesting a new workflow dispatch for {$owner}/{$repo} using workflow: {$workflow}",
        );
 
        $response = $request->send();
 
        if ($response->failed()) {
            throw $response->toException();
        }
 
        $this->info(
            string: 'Request was accepted by GitHub',
        );
 
        return self::SUCCESS;
    }
}
on: workflow_dispatch

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