Favicons : quand les petits pixels font les grands sites
Un favicon devant un lien, c’est le sourire du site qui te dit : oui, tu peux cliquer, je connais.
On va parler du HTTP Client de Symfony, cette petite pépite qui fait les requêtes HTTP comme un ministre qui évite les convocations : vite fait, bien fait, parfaitement exécuté.te.
Le HTTP Client de Symfony, c'est comme si tu prenais le bon vieux cURL, que tu lui donnais un costard trois pièces, une éducation dans les meilleures écoles et un abonnement premium à la salle de sport. C'est une abstraction élégante qui te permet de faire des requêtes HTTP sans te prendre la tête avec les détails techniques qui donnent des cauchemars à ton stagiaire.
Concrètement, c'est un service qui te permet de :
composer require symfony/http-client
En un seul coup de marteau, et tu as le service. Pas de configuration compliquée, pas de XML à écrire, pas de sacrifice de poulet à minuit. Simple, efficace, à l'image d'un prélude de Bach.
Quand j'ai dû récupérer des statistiques de pages depuis Umami (un outil d'analytics qui respecte ta vie privée plus que ton ex), j'ai créé un petit service qui utilise le HTTP Client pour interroger leur API. Voici à quoi ça ressemble :
// src/Service/Analytic/UmamiClient.php
namespace App\Service\Analytic;
use DateTimeImmutable;
use DateTimeInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Contracts\HttpClient\HttpClientInterface;
readonly class UmamiClient
{
public function __construct(
#[Autowire('%umami_base_url%')] private string $baseUrl,
#[Autowire('%umami_api_key%')] private string $apiKey,
private HttpClientInterface $httpClient,
) {
}
public function getMetrics(
string $websiteId,
string $type = 'url',
?DateTimeInterface $startDate = null,
?DateTimeInterface $endDate = null,
): array {
// Validation du type de métrique
$allowedTypes = ['url', 'event', 'page', 'referrer', 'browser', 'os', 'device', 'country'];
if (!\in_array($type, $allowedTypes, true)) {
throw new InvalidArgumentException(
\sprintf(
'Type de métrique invalide : "%s". Types autorisés : %s',
$type,
implode(', ', $allowedTypes),
),
);
}
// Préparation des dates
$now = new DateTimeImmutable();
$defaultStartDate = (new DateTimeImmutable('2020-01-01'));
$effectiveStartDate = $startDate ?? $defaultStartDate;
$effectiveEndDate = $endDate ?? $now;
// Paramètres de requête
$params = [
'type' => $type,
'startAt' => $effectiveStartDate->getTimestamp() * 1000,
'endAt' => $effectiveEndDate->getTimestamp() * 1000,
];
// Construction de l'URL
$url = \sprintf(
'%s/websites/%s/metrics',
rtrim($this->baseUrl, '/'),
urlencode($websiteId),
);
// La magie opère ici
$response = $this->httpClient->request('GET', $url, [
'headers' => [
'x-umami-api-key' => $this->apiKey,
],
'query' => $params,
]);
// Transformation de la réponse en tableau (aussi facile que de transformer tes économies en bières un vendredi soir)
try {
return $response->toArray();
} catch (Exception) {
// En cas d'erreur, on retourne un tableau vide
// Comme l'assiette de ton coloc quand t'as fait les courses
//...
}
return [];
}
}
J'ai créé une commande qui utilise ce service pour récupérer les données et les envoyer à un bus de Messages Symfony. C'est comme un système de livraison de données, mais sans le livreur qui te demande un pourboire alors qu'il a écrasé ton colis.
// src/Command/Analytic/FetchMetricsStatsCommand.php
namespace App\Command\Analytic;
use App\Message\Analytic\ProcessPageMetricsMessage;
use App\Service\Analytic\UmamiClient;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\Messenger\MessageBusInterface;
#[AsCommand(
name: 'app:analytics:fetch-stats',
description: 'Fetch Umami stats and dispatch messages to process them',
)]
class FetchMetricsStatsCommand extends Command
{
public function __construct(
private readonly UmamiClient $umamiClient,
private readonly PageViewTransformer $pageViewTransformer,
private readonly MessageBusInterface $messageBus,
#[Autowire('%umami_website_id%')] private readonly string $websiteId,
) {
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$output->writeln('Récupération des statistiques depuis Umami...');
try {
// Récupération des métriques
$umamiData = $this->umamiClient->getMetrics(
websiteId: $this->websiteId,
);
// Transformation des données brutes en objets
$metrics = $this->pageViewTransformer->transformUmamiData($umamiData);
// Envoi des messages au bus
foreach ($metrics as $metric) {
$message = new ProcessPageMetricsMessage(
$metric->getUrl(),
$metric->getViews(),
);
$this->messageBus->dispatch($message);
}
return Command::SUCCESS;
} catch (Exception $exception) {
$output->writeln('<error>Erreur : '.$exception->getMessage().'</error>');
return Command::FAILURE;
}
}
}
C'est pas juste un simple client HTTP, c'est un couteau suisse avec la précision d'un scalpel suisse et l'efficacité d'une montre suisse. Voici quelques fonctionnalités qui déchirent :
$response = $httpClient->request('GET', 'https://api.example.com/data');
// Fais d'autres trucs pendant que la requête se fait...
$result = $response->toArray(); // La requête est réellement exécutée ici
Pendant que ton ex met 3 heures à te répondre, la requête HTTP, elle, attend patiemment son moment pour s'exécuter sans bloquer ton code.
$responses = [];
$responses[] = $httpClient->request('GET', 'https://api.example.com/data1');
$responses[] = $httpClient->request('GET', 'https://api.example.com/data2');
$responses[] = $httpClient->request('GET', 'https://api.example.com/data3');
// Exécuter toutes les requêtes en parallèle
foreach ($responses as $response) {
// Chaque itération attend la fin de sa requête
$data = $response->toArray();
// Fais quelque chose avec $data
}
C'est comme envoyer trois potes chercher de la bière en même temps au lieu de les envoyer un par un. Optimisation, mon amour !
$response = $httpClient->request('GET', 'https://api.example.com/data', [
'timeout' => 2.5,
]);
Parce que parfois, c'est comme un rencard Tinder : si ça répond pas rapidement, vaut mieux passer à autre chose.
$response = $httpClient->request('GET', 'https://api.example.com/data', [
'auth_basic' => ['username', 'password'],
// OU
'auth_bearer' => 'token...',
]);
Parce que même sur internet, y'a des clubs VIP.
Ne stocke JAMAIS tes clés API dans ton code : Utilise les variables d'environnement ou les secrets, comme je l'ai fait avec l'attribut #[Autowire].
Gère les erreurs comme un adulte : Une API externe, c'est comme la météo ou l'humeur de ton/ta partenaire - imprévisible. Prépare-toi à l'échec.
Mets des timeouts : Sans timeout, ton application peut se retrouver bloquée plus longtemps qu'un député en séance de nuit.
Cache tes réponses quand c'est possible : Parce que bombarder une API comme si c'était un serveur Minecraft, c'est pas très élégant.
Le bundle HTTP Client de Symfony, c'est un peu comme trouver un billet de 50€ dans un jean que t'as pas mis depuis longtemps : une agréable surprise qui te facilite la vie. Simple, efficace, puissant, il me permet de récupérer mes données d'analytics sans me prendre la tête, et de les traiter proprement.
Si tu fais encore tes requêtes HTTP à la main ou avec des librairies d'un autre âge, c'est peut-être le moment de passer à la vitesse supérieure. À moins que tu sois nostalgique de l'époque où on devait composer des numéros de téléphone sur des cadrans rotatifs ?