Aller au contenu principal
Version: Canary đźš§

Conteneur de services

Introduction​

Le conteneur d'injection de dépendances (DIC) de Bow Framework, appelé Capsule, est un outil puissant pour gérer les dépendances de votre application.

Injection de dépendances (DI)​

L'injection de dépendances permet de fournir les objets dont une classe a besoin plutôt que de les créer elle-même. Cela rend le code plus modulaire et testable.

Inversion de contrôle (IoC)​

L'inversion de contrôle délègue la gestion des objets et de leurs dépendances à un conteneur. Le conteneur crée et gère les objets nécessaires pour vous.

Accéder au conteneur​

Via le helper app()​

// Obtenir l'instance du conteneur
$container = app();

// Résoudre directement une classe
$service = app(App\Services\UserService::class);

Via la classe Capsule​

use Bow\Container\Capsule;

$container = Capsule::getInstance();

Enregistrer des liaisons​

Liaison simple (bind)​

Enregistre un service qui sera instancié une seule fois, puis mis en cache:

use App\Services\PaymentService;

app()->bind('payment', function (Capsule $container) {
return new PaymentService();
});

// Ou avec une classe
app()->bind(PaymentServiceInterface::class, PaymentService::class);

Instance (singleton)​

Enregistre une instance déjà créée:

use App\Services\Config;

$config = new Config(['debug' => true]);
app()->instance('config', $config);

Fabrique (factory)​

Crée une nouvelle instance à chaque résolution:

app()->factory('uuid', function (Capsule $container) {
return new UuidGenerator();
});

// Chaque appel crée une nouvelle instance
$uuid1 = app()->make('uuid');
$uuid2 = app()->make('uuid'); // Instance différente

Résoudre des dépendances​

Résolution simple​

// Via make()
$service = app()->make('payment');

// Via le helper
$service = app('payment');

// Via une classe
$service = app(App\Services\PaymentService::class);

Résolution avec paramètres​

$service = app()->makeWith(App\Services\ReportService::class, [
'startDate' => '2024-01-01',
'endDate' => '2024-12-31'
]);

Accès par tableau​

Le conteneur implémente ArrayAccess:

// Vérifier l'existence
if (isset(app()['payment'])) {
// Le service existe
}

// Récupérer un service
$service = app()['payment'];

// Enregistrer un service
app()['cache'] = function () {
return new CacheService();
};

// Supprimer un service
unset(app()['cache']);

Injection automatique​

Le conteneur résout automatiquement les dépendances des constructeurs:

namespace App\Services;

use App\Repositories\UserRepository;
use App\Services\EmailService;

class UserService
{
public function __construct(
private UserRepository $repository,
private EmailService $emailService
) {
}

public function register(array $data): User
{
$user = $this->repository->create($data);
$this->emailService->sendWelcome($user);
return $user;
}
}

Lorsque vous demandez UserService, le conteneur:

  1. Détecte les dépendances du constructeur
  2. Résout UserRepository et EmailService
  3. Instancie UserService avec ces dépendances
// Le conteneur crée automatiquement toutes les dépendances
$userService = app(App\Services\UserService::class);

Liaison d'interfaces​

Liez une interface à son implémentation concrète:

// Enregistrement
app()->bind(
App\Contracts\PaymentGatewayInterface::class,
App\Services\StripePaymentGateway::class
);

// Utilisation dans une classe
class OrderService
{
public function __construct(
private PaymentGatewayInterface $gateway
) {
}
}

// Le conteneur injecte StripePaymentGateway
$orderService = app(OrderService::class);

Utilisation dans les contrôleurs​

Les dépendances sont automatiquement injectées dans les contrôleurs:

namespace App\Http\Controllers;

use App\Services\UserService;
use Bow\Http\Request;

class UserController
{
public function __construct(
private UserService $userService
) {
}

public function index(Request $request)
{
return $this->userService->getAllUsers();
}

// Injection par méthode
public function show(Request $request, UserService $service)
{
return $service->find($request->get('id'));
}
}

Exemple complet​

Définir les interfaces​

namespace App\Contracts;

interface UserRepositoryInterface
{
public function find(int $id): ?User;
public function create(array $data): User;
public function update(int $id, array $data): bool;
public function delete(int $id): bool;
}

interface NotificationServiceInterface
{
public function send(User $user, string $message): void;
}

Implémenter les interfaces​

namespace App\Repositories;

use App\Contracts\UserRepositoryInterface;
use App\Models\User;

class UserRepository implements UserRepositoryInterface
{
public function find(int $id): ?User
{
return User::retrieve($id);
}

public function create(array $data): User
{
return User::create($data);
}

public function update(int $id, array $data): bool
{
return (bool) User::where('id', $id)->update($data);
}

public function delete(int $id): bool
{
return (bool) User::deleteBy('id', $id);
}
}
namespace App\Services;

use App\Contracts\NotificationServiceInterface;
use App\Models\User;

class EmailNotificationService implements NotificationServiceInterface
{
public function send(User $user, string $message): void
{
// Envoyer un email
mail($user->email, 'Notification', $message);
}
}

Créer le service​

namespace App\Services;

use App\Contracts\UserRepositoryInterface;
use App\Contracts\NotificationServiceInterface;

class UserService
{
public function __construct(
private UserRepositoryInterface $repository,
private NotificationServiceInterface $notifier
) {
}

public function register(array $data): User
{
$user = $this->repository->create($data);
$this->notifier->send($user, 'Bienvenue sur notre plateforme!');
return $user;
}

public function getUser(int $id): ?User
{
return $this->repository->find($id);
}
}

Enregistrer dans un ServiceProvider​

namespace App\Configurations;

use Bow\Configuration\Configuration;
use Bow\Configuration\Loader;
use App\Contracts\UserRepositoryInterface;
use App\Contracts\NotificationServiceInterface;
use App\Repositories\UserRepository;
use App\Services\EmailNotificationService;

class AppServiceProvider extends Configuration
{
public function create(Loader $config): void
{
// Lier les interfaces aux implémentations
$this->container->bind(
UserRepositoryInterface::class,
UserRepository::class
);

$this->container->bind(
NotificationServiceInterface::class,
EmailNotificationService::class
);
}

public function run(): void
{
//
}
}

Utiliser dans un contrôleur​

namespace App\Http\Controllers;

use App\Services\UserService;
use Bow\Http\Request;

class UserController
{
public function __construct(
private UserService $userService
) {
}

public function show(int $id)
{
$user = $this->userService->getUser($id);

if (!$user) {
return response()->json(['error' => 'User not found'], 404);
}

return response()->json($user);
}

public function store(Request $request)
{
$user = $this->userService->register(
$request->only(['name', 'email', 'password'])
);

return response()->json($user, 201);
}
}

Bonnes pratiques​

Recommandations
  1. Injection par constructeur: Déclarez clairement les dépendances d'une classe
  2. Interfaces plutôt que classes concrètes: Gardez votre code flexible et testable
  3. Un service, une responsabilité: Chaque service doit avoir une seule raison de changer
  4. Évitez les singletons abusifs: Utilisez bind() par défaut, instance() uniquement si nécessaire
  5. Enregistrez dans les ServiceProviders: Centralisez la configuration du conteneur

Méthodes du conteneur​

MéthodeDescription
bind($key, $value)Enregistre un service (singleton après première résolution)
factory($key, $value)Enregistre une fabrique (nouvelle instance Ă  chaque fois)
instance($key, $object)Enregistre une instance existante
make($key)Résout un service
makeWith($key, $params)Résout avec des paramètres personnalisés

Il manque quelque chose ?

Si vous rencontrez des problèmes avec la documentation ou si vous avez des suggestions pour améliorer la documentation ou le projet en général, veuillez déposer une issue pour nous, ou envoyer un tweet mentionnant le compte Twitter @bowframework ou sur directement sur le github.

Avantages

Grâce à ces principes, vous pouvez créer des applications plus propres, plus faciles à maintenir et plus robustes.

Il manque quelque chose ?

Si vous rencontrez des problèmes avec la documentation ou si vous avez des suggestions pour améliorer la documentation ou le projet en général, veuillez déposer une issue pour nous, ou envoyer un tweet mentionnant le compte Twitter @bowframework ou sur directement sur le github.