Skip to content

Instantly share code, notes, and snippets.

@Fludem
Created April 17, 2024 03:59
Show Gist options
  • Save Fludem/3a5b7a69a42a674c03b4c11e08431fe4 to your computer and use it in GitHub Desktop.
Save Fludem/3a5b7a69a42a674c03b4c11e08431fe4 to your computer and use it in GitHub Desktop.

Livewire Component

<?php

namespace App\Livewire;

use App\DTO\SongDTO;
use App\Services\SongAPIs\Contracts\SongAPIService;
use Livewire\Attributes\Lazy;
use Livewire\Component;

#[Lazy]
class TitleSong extends Component
{
    private SongAPIService $songAPIService;

    public SongDTO $song;

   public function boot(SongAPIService $songAPIService): void
   {
       $this->songAPIService = $songAPIService;
   }

    public function mount(int $songId): void
    {
      $this->resolveSong($songId);
    }

    public function resolveSong(): void
    {
         try {
             $this->song = $this->songAPIService->getSongData(1);
         } catch (\Exception $exception) {
             // You need to handle this exception for UX & Debugging purposes
         }
    }
}

Changes:

Mount method now accepts a Song ID so we can render the relevant song

SongDTO usage so we have a defined Song Object.

Resolve Song Method

Type Hinting the return types on methods like Resolve Song (even though it's just void)

The Boot Method accepts an instance of SongAPIService

Note: SongAPIService is an interface in the case we want to add tests or switch APIs in future

Song DTO Class

<?php

namespace App\DTO;

class SongDTO {


    public function __construct(
        public string $songArtist,
        public int $secondsElapsed,
        public int $secondsTotal,
        public string $songImage,
        public string $songTitle,
        public int $remainingTime
    ) {}
}

This accepts all of the values you'd expect a song to have in its constructor.

This is a very basic example, you may add more functionality such as Getters & Setters.

By using a Data Object we can define that Songs have to have these variables.

If we just use Arrays your IDE will not type hint or be able to guess what parameters it contains.

SpotifyAPIService

I don't know what API You're using but this is a concrete class with psuedo code.

This class gets the song data from an API and creates the SongDTO Object I mentioned earlier.

<?php
declare(strict_types=1);

namespace App\Services\SongAPIs;

use App\DTO\SongDTO;
use App\Services\SongAPIs\Contracts\SongAPIService;
use App\Services\SongAPIs\Concerns\CreatesSongDTO;
use App\Services\SongAPIs\Concerns\SendsRequests;
use Exception;

class SpotifyAPIService implements SongAPIService {

    use SendsRequests;
    use CreatesSongDTO;

    /**
     * @throws Exception
     */
    public function getSongData(int $songId): SongDTO
    {
        $response = $this->sendGetRequest($this->getEndpointUrl($songId));
        return $this->createSong($response);
    }

    public function getEndpointUrl(int $songId): string
    {
        return "https://api.spotify.com/v1/songs/{$songId}";
    }

}

It's worth noting we are using traits and an interface here, I will include those too.

Song API Service Interface

An interface allows us to specify some methods that any class implementing it must have.

This allows you to switch APIs or Use a Test Service that creates songs from a Factory etc as I mentioned earlier

<?php

declare(strict_types=1);

namespace App\Services\SongAPIs\Contracts;

use App\DTO\SongDTO;
use Exception;

interface SongAPIService {
    public function getEndpointUrl(int $songId): string;

    /** @throws Exception */
    public function getSongData(int $songId): SongDTO;
}

Sends Requests Trait

<?php

namespace App\Services\SongAPIs\Concerns;

use Exception;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http;

trait SendsRequests {

    /**
     * @throws Exception
     */
    public function sendGetRequest(string $url): Response
    {
            return Http::get($url);
    }

}

Creates Song DTO Trait

<?php

declare(strict_types=1);

namespace App\Services\SongAPIs\Concerns;

use App\DTO\SongDTO;
use Illuminate\Http\Client\Response;

trait CreatesSongDTO {

    public function createSong(Response|array|string $data): SongDTO
    {
        return $data instanceof Response ? $this->createFromResponse($data) : $this->createFromArray($data);
    }

    private function createFromArray(array $data): SongDTO
    {
        return new SongDTO(
            $data['song']['artist'],
            $data['song']['seconds_elapsed'],
            $data['song']['seconds_total'],
            $data['song']['art'],
            $data['song']['title'],
            $data['song']['seconds_total'] - $data['song']['seconds_elapsed']
        );
    }
    private function createFromResponse(Response $response): SongDTO
    {
        return $this->createFromArray($response->json());
    }

}

I should probably go to bed so I didn't spend much time explaining all of this code, but I hope it's intuitive as it's in the context of your application.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment