Servicio GitHub: Implementación
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeAhora que tenemos una idea de lo que necesitamos que haga el GithubService
, vamos a añadir la lógica interna que obtendrá las incidencias del repositorio dino-park
utilizando la API de GitHub.
Añade el cliente y haz una petición
Para hacer peticiones HTTP, en tu terminal, instala el Cliente HTTP de Symfony con:
composer require symfony/http-client
Dentro de GithubService
, instala un cliente HTTP con$client = HttpClient::create()
. Para hacer una petición, llama a $client->request()
. Esto necesita 2 cosas. 1ª: qué método HTTP utilizar, como GET
o POST
. En este caso, debería ser GET
. 2ª: la URL, que pegaré. Esto recuperará todos los "issues" del repositorio dino-park
a través de la API de GitHub.
// ... lines 1 - 5 | |
use Symfony\Component\HttpClient\HttpClient; | |
class GithubService | |
{ | |
public function getHealthReport(string $dinosaurName): HealthStatus | |
{ | |
// ... lines 12 - 13 | |
$client = HttpClient::create(); | |
$response = $client->request( | |
method: 'GET', | |
url: 'https://api.github.com/repos/SymfonyCasts/dino-park/issues' | |
); | |
// ... lines 20 - 27 | |
} | |
} |
Analiza la respuesta HTTP
Bien, ¿y ahora qué? Mirando el repositorio dino-park
, GitHub devolverá una respuesta JSON que contiene las incidencias que vemos aquí. Cada incidencia tiene un título con el nombre de un dino y si la incidencia tiene una etiqueta adjunta, también la obtendremos de vuelta. Así que, pon $client->request()
en una nueva variable $response
. A continuación, foreach()
sobre $response->toArray()
como $issue
. Lo bueno de utilizar el cliente HTTP de Symfony es que no tenemos que molestarnos en transformar el JSON de GitHub en una matriz - toArray()
hace ese trabajo pesado por nosotros. Dentro de este bucle, comprobamos si el título de la incidencia contiene el $dinosaurName
. Así queif (str_contains($issue['title'], $dinosaurName))
entonces // Do Something
con esa incidencia.
// ... lines 1 - 5 | |
use Symfony\Component\HttpClient\HttpClient; | |
class GithubService | |
{ | |
public function getHealthReport(string $dinosaurName): HealthStatus | |
{ | |
// ... lines 12 - 13 | |
$client = HttpClient::create(); | |
$response = $client->request( | |
method: 'GET', | |
url: 'https://api.github.com/repos/SymfonyCasts/dino-park/issues' | |
); | |
foreach ($response->toArray() as $issue) { | |
if (str_contains($issue['title'], $dinosaurName)) { | |
} | |
} | |
// ... lines 26 - 27 | |
} | |
} |
Llegados a este punto, hemos encontrado la incidencia de nuestro dinosaurio. ¡Vaya! Ahora tenemos que hacer un bucle sobre cada etiqueta para ver si podemos encontrar el estado de salud. Para ayudarte, voy a pegar un método privado: puedes copiarlo del bloque de código de esta página.
// ... lines 1 - 4 | |
use App\Enum\HealthStatus; | |
// ... lines 6 - 7 | |
class GithubService | |
{ | |
// ... lines 10 - 29 | |
private function getDinoStatusFromLabels(array $labels): HealthStatus | |
{ | |
$status = null; | |
foreach ($labels as $label) { | |
$label = $label['name']; | |
// We only care about "Status" labels | |
if (!str_starts_with($label, 'Status:')) { | |
continue; | |
} | |
// Remove the "Status:" and whitespace from the label | |
$status = trim(substr($label, strlen('Status:'))); | |
} | |
return HealthStatus::tryFrom($status); | |
} | |
} |
Esto toma una matriz de etiquetas... y cuando encuentra una que empieza por Status:
, devuelve el enum correcto HealthStatus
basado en esa etiqueta.
Ahora, en lugar de // Do Something
, decimos$health = $this->getDinoStatusFromLabels()
y pasamos las etiquetas con $issue['labels']
.
// ... lines 1 - 5 | |
use Symfony\Component\HttpClient\HttpClient; | |
class GithubService | |
{ | |
public function getHealthReport(string $dinosaurName): HealthStatus | |
{ | |
// ... lines 12 - 13 | |
$client = HttpClient::create(); | |
$response = $client->request( | |
method: 'GET', | |
url: 'https://api.github.com/repos/SymfonyCasts/dino-park/issues' | |
); | |
foreach ($response->toArray() as $issue) { | |
if (str_contains($issue['title'], $dinosaurName)) { | |
$health = $this->getDinoStatusFromLabels($issue['labels']); | |
} | |
} | |
// ... lines 26 - 27 | |
} | |
// ... lines 30 - 49 |
Y ahora podemos devolver $health
. Pero... ¿qué pasa si un número no tiene una etiqueta de estado de salud? Hmm... al principio de este método, establece el valor por defecto $health
a HealthStatus::HEALTHY
- porque GenLab nunca se olvidaría de poner una etiquetaSick
en un dino que no se encuentra bien.
// ... lines 1 - 7 | |
class GithubService | |
{ | |
public function getHealthReport(string $dinosaurName): HealthStatus | |
{ | |
$health = HealthStatus::HEALTHY; | |
$client = HttpClient::create(); | |
$response = $client->request( | |
method: 'GET', | |
url: 'https://api.github.com/repos/SymfonyCasts/dino-park/issues' | |
); | |
foreach ($response->toArray() as $issue) { | |
if (str_contains($issue['title'], $dinosaurName)) { | |
$health = $this->getDinoStatusFromLabels($issue['labels']); | |
} | |
} | |
return $health; | |
} | |
// ... lines 29 - 49 |
Hmm... Bueno, ¡creo que lo hemos conseguido! Hagamos nuestras pruebas para estar seguros.
./vendor/bin/phpunit
Y... ¡Vaya! Tenemos 8 pruebas, 11 aserciones, ¡y todas pasan! ¡Shweeet!
Registra todas nuestras peticiones
¡Un último reto! Para ayudar a la depuración, quiero registrar un mensaje cada vez que hagamos una petición a la API de GitHub.
¡No hay problema! Sólo tenemos que conseguir el servicio de registro. Añade un constructor conprivate LoggerInterface $logger
para añadir un argumento y una propiedad de una sola vez. Justo después de llamar al método request()
, añade $this->logger->info()
y pasaRequest Dino Issues
para el mensaje y también un array con contexto extra. ¿Qué tal una clave dino
establecida en $dinosaurName
y responseStatus
en$response->getStatusCode()
.
// ... lines 1 - 5 | |
use Psr\Log\LoggerInterface; | |
// ... lines 7 - 8 | |
class GithubService | |
{ | |
public function __construct(private LoggerInterface $logger) | |
{ | |
} | |
public function getHealthReport(string $dinosaurName): HealthStatus | |
{ | |
// ... lines 17 - 25 | |
$this->logger->info('Request Dino Issues', [ | |
'dino' => $dinosaurName, | |
'responseStatus' => $response->getStatusCode(), | |
]); | |
// ... lines 30 - 37 | |
} | |
// ... lines 39 - 57 | |
} |
¡Genial! Eso no debería haber roto nada en nuestra clase, pero vamos a ejecutar las pruebas para estar seguros:
./vendor/bin/phpunit
Y... ¡Ay! ¡Sí que hemos roto algo!
Se han pasado muy pocos argumentos al constructor de GithubService. se esperaban 0 pasados 1.
¡Por supuesto! Cuando añadimos el argumento LoggerInterface
a GithubService
, nunca actualizamos nuestro test para pasarlo. Te mostraré cómo podemos hacerlo a continuación utilizando una de las super habilidades de PHPUnit: el mocking.
Wouldn't it be better to use the Symfony\Contracts\HttpClient\HttpClientInterface? If not, what would be the disadvantages?