Apprendre Symfony 6 : Créer un Service

Les services sont des composants modulaires et réutilisables pour votre application Symfony. Découvrons comment créer un service.

Icône de calendrier
Intermédiaire
13 chapitres

Comprendre les services

Qu’est-ce qu’un service ?

Un service dans Symfony est un composant réutilisable qui effectue une tâche spécifique, facilitant la modularité et la gestion des dépendances dans une application.

Un service peut exécuter tout type de tâche, comme par exemple :

  • Envoyer un email
  • Journaliser des informations
  • Traiter des images (redimensionnement, compression…)
  • Mettre en cache des données
  • Gérer des paiements en ligne (via Stripe, PayPal…)
  • Envoyer des notifications
  • Etc.

Conteneur de services

Au sein du framework Symfony, la gestion des services est gérée par le conteneur de service (ou en anglais « Service Container »).

Le conteneur de services est l’un des composants centraux du framework Symfony. Il s’agit d’un conteneur d’injection de dépendances qui gère la création et la gestion des services au sein d’une application Symfony.

Le conteneur de services a plusieurs rôles essentiels dans une application Symfony :

  1. Gestion de dépendances : Il simplifie la gestion des dépendances en les injectant automatiquement lorsque nécessaire, favorisant la réutilisation du code et la séparation des préoccupations. Par exemple, un service de journalisation peut être injecté dans un service de gestion de base de données pour le débogage.

  2. Configuration centralisée : Il centralise la gestion et la configuration de tous les services de l’application, permettant des modifications sans altérer le code source, souvent à l’aide de fichiers de configuration comme YAML, XML ou PHP.

  3. Optimisation des performances : Il permet d’optimiser les performances en créant les services uniquement lorsque vous en avez besoin. Il peut également gérer la portée des services.

  4. Gestion des paramètres : Il peut également gérer les paramètres, qui sont des valeurs configurables utilisées dans votre application.

Utiliser un service

À la création d’un nouveau projet Symfony, le conteneur contient déjà de nombreux services.

Ces services sont alors désormais accessibles depuis nos méthodes de contrôleur en typant un paramètre par le nom de classe du service en question. C’est la mécanique de « l’autowiring », basée sur le typage des paramètres.

DemoController.php
copié !
namespace App\Controller;

use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class DemoController extends AbstractController {

	#[Route('/', name: 'app_index')]
	public function index(LoggerInterface $logger): Response {
		$logger->info('Ce texte sera logué dans le fichier var/log/dev.log');
		// ...
	}

}

Si Symfony intègre initialement de nombreux services utiles au sein de son conteneur, il est bien entendu possible d’en greffer d’autres via votre gestionnaire de paquet favori. Et si vous ne trouvez pas votre bonheur sur Packagist parce que votre besoin est très spécifique, vous avez alors la possibilité de créer votre propre service !

Créer un service

Si vous souhaitez créer un service pour les besoins de votre application web, la démarche est plutôt simple.

Le dossier 📂 Service contiendra l’ensemble de nos services, définis au sein de classes PHP.

Créons par exemple un service dédié à l’analyse d’articles de blog. Ce service va entre autres retourner :

  • Le temps de lecture d’un article (en se basant sur le nombre de mots qu’il contient)
  • Un extrait des x premiers mots d’un article puis ajout ... à la fin
src/Service/TextParser.php
copié !
namespace App\Service;

class TextParser {

	public function readingTime(string $content): float {
		$nbWordsByMinute = 200;
		$nbWords = str_word_count($content);
		return ceil($nbWords / $nbWordsByMinute);
	}

	public function generateSummary(string $content, int $nbWords = 20): string {
		$content = strip_tags($content);
		$words = explode(' ', $content);
		$summary = implode(' ', array_slice($words, 0, $nbWords));
		if (count($words) > $nbWords) {
			$summary .= '...';
		}
		return $summary;
	}

}

Notre service fraîchement créé étant automatiquement géré par le conteneur de services, peut être utilisé classiquement avec l’autowiring :

src/Service/TextParser.php
copié !
namespace App\Controller;

use App\Service\TextParser;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class DemoController extends AbstractController {

	#[Route('/', name: 'app_index')]
	public function index(TextParser $textParser): Response {
		$text = "Lorem ipsum dolor sit amet consectetur adipisicing elit. Laboriosam neque inventore magni, expedita eius esse veritatis suscipit itaque asperiores hic reprehenderit odit sapiente voluptates labore, necessitatibus aliquid consequatur. Sapiente, quo? Lorem ipsum dolor sit amet consectetur adipisicing elit.";
		$readingTime = $textParser->readingTime($text);
		$extract = $textParser->generateExtract($text);
		// ...
	}

}

Injection de dépendances

Si l’un de vos services A nécessite d’utiliser un service B, alors il sera possible d’injecter le service B en tant que paramètre dans le constructeur du service A. On parle d’injection de dépendance.

src/Service/TextParser.php
copié !
namespace App\Service;

use Psr\Log\LoggerInterface;

class TextParser {

	public function __construct(private LoggerInterface $logger) {}

	public function readingTime(string $content): float {
		$this->logger->info("Calcul du temps de lecture.");
		$nbWordsByMinute = 200;
		$nbWords = str_word_count($content);
		return ceil($nbWords / $nbWordsByMinute);
	}

	public function generateSummary(string $content, int $nbWords = 20): string {
		$this->logger->info("Création d'un résumé.");
		$content = strip_tags($content);
		$words = explode(' ', $content);
		$summary = implode(' ', array_slice($words, 0, $nbWords));
		if (count($words) > $nbWords) {
			$summary .= '...';
		}
		return $summary;
	}

}

Si d’autres services avaient été requis par le service TextParser, alors il aurait suffi de les ajouter eux aussi en paramètre du constructeur.

src/Service/TextParser.php
copié !
namespace App\Service;

use ...\Service1;
use ...\Service2;

class TextParser {

	public function __construct(private Service1 $s1, private Service2 $s2...) {}

}

Configuration

Le fichier 📄 config/services.yaml dans Symfony est utilisé pour définir et configurer les services propres à votre application, ainsi que leurs dépendances. Voici quelques paramétrages couramment utilisés dans ce fichier.

Configuration spécifique

Il peut être utile d’ajouter dans votre configuration une donnée qui sera utilisée par votre service. Il peut par exemple s’agir :

  • D’un email d’administrateur pour un service d’emailing
  • La langue utilisée (fr, en…)
  • Etc.

Cette donnée pourra être définie au sein d’un argument.

Déclaration

Par convention, les arguments sont définis sous la clé arguments dans le fichier 📄 config/services.yaml :

config/services.yaml
copié !
services:

	# ...

	# Configuration de mon service TextParser
	App\Service\TextParser:
		arguments:
			$lang: 'fr'

Utilisation

Cette donnée est ensuite exploitable en la déclarant dans le constructeur de votre service. Il sera alors possible d’y faire référence classiquement avec l’opérateur $this.

src/Service/TextParser.php
copié !
namespace App\Service;

class TextParser {

	public function __construct(private string $lang) {}

	public function readingTime(string $content): float {
		// Je peux utiliser $this->lang
	}

}

Configuration partagée

Parfois, une même donnée de votre configuration est utilisée dans plusieurs services. Au lieu de dupliquer cette information dans votre configuration, vous pouvez la définir en tant que paramètre.

Un paramètre de service est une valeur de configuration réutilisable et partagée.

Ici, il pourrait être intéressant de stocker fr dans un paramètre lang plutôt que dans la configuration du service TextParser. Cela permettrait à d’autres services de l’exploiter, comme par exemple :

  • Un service de traduction
  • Un service de correction orthographique
  • Un service de formatage de dates et heures
  • Etc.

Déclaration

Par convention, les paramètres sont définis sous la clé parameters dans le fichier 📄 config/services.yaml :

config/services.yaml
copié !
parameters:
	app.lang: 'fr'

# ...

Utilisation

Vos paramètres de services peuvent être utilisées par les services souhaités via leur configuration .yaml.

Il est possible de faire référence aux paramètres de service déclarés via parameters depuis la clé arguments.

Pour cela, il suffit de noter entre pourcentages % le nom du paramètre.

config/services.yaml
copié !
parameters:
	app.lang: 'fr'

# ...

services:
	App\Service\TextParser:
		arguments:
			$lang: '%app.lang%'

Avec cette configuration, Symfony injectera la valeur du paramètre app.lang dans le service textParser, lorsqu’il sera instancié.

Si vous souhaitez utiliser votre argument $lang depuis le service, vous pouvez procéder de la même manière que précédemment, en tant que paramètre du constructeur.

src/Service/TextParser.php
copié !
namespace App\Service;

class TextParser {

	public function __construct(private string $lang) {}

	// ...

}

Ce chapitre aborde les mécaniques élémentaires concernant la gestion des Services, mais Symfony propose de nombreuses autres options de configuration sur sa documentation officielle.