<?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
<?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.
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.
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;
}
<?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);
}
}
<?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.