andydefer/php-services

Collection of reusable services for PHP and Laravel applications including model transformation, caching, and common business logic

Maintainers

Package info

github.com/andydefer/php-services

Language:Makefile

pkg:composer/andydefer/php-services

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

0.1.1 2026-06-06 19:11 UTC

This package is auto-updated.

Last update: 2026-06-06 19:11:52 UTC


README

PHP Version Laravel Version License

Installation

composer require andydefer/php-services

Philosophie du package

Ce package est une collection de services réutilisables pour PHP et Laravel.

Il s'inspire de plusieurs principes du génie logiciel qui servent de boussole, pas de carcan :

Principe Ce qu'il encourage Ce qu'il n'impose pas
Composition Over Inheritance Préférer l'injection de dépendances à l'héritage L'héritage reste possible quand il est pertinent
Dependency Inversion Dépendre des interfaces plutôt que des classes concrètes Les DTOs et Value Objects peuvent être concrets
Capability-Based Design Exposer des capacités spécifiques plutôt que des services fourre-tout Un service peut avoir plusieurs méthodes cohésives
Domain-Driven Design Organiser le code par domaine fonctionnel La structure peut évoluer librement

L'objectif est d'obtenir un code testable, découplé et maintenable, sans tomber dans l'extrémisme architectural.

Ce qu'un Service est

// ✅ Un service = un conteneur de méthodes qui partagent un même domaine
class OrderCalculatorService
{
    // ✅ Des dépendances injectées dans le constructeur
    public function __construct(
        private readonly TaxService $taxService,
        private readonly OrderConfig $config,
    ) {}
    
    // ✅ Des méthodes qui reçoivent leurs données en paramètres
    public function calculateTotal(OrderRecord $order): float
    {
        $subtotal = $this->calculateSubtotal($order);
        $tax = $this->taxService->calculate($subtotal);
        
        return $subtotal + $tax;
    }
    
    // ✅ Pas d'état interne, pas de mémoire entre les appels
    private function calculateSubtotal(OrderRecord $order): float
    {
        return array_reduce($order->items, fn($c, $i) => $c + ($i->price * $i->quantity), 0);
    }
}

Pourquoi cette philosophie ?

Problème : Les traits (anti-pattern)

// ❌ Un trait : impossible à tester isolément
trait FileCreator
{
    private Filesystem $files;
    
    public function createFile(string $path, string $content): bool
    {
        $this->files = new Filesystem();  // Dépendance cachée
        return $this->files->put($path, $content);
    }
}

class TaskDirective extends AbstractDirective
{
    use FileCreator;  // ❌ Couplage implicite, test impossible
}

Solution : Le service

// ✅ Un service : testable, injectable, découplé
class FileCreatorService
{
    public function __construct(
        private readonly Filesystem $files,  // ✅ Injection explicite
    ) {}
    
    public function createFile(string $path, string $content): bool
    {
        return $this->files->put($path, $content);
    }
}

class TaskDirective extends AbstractDirective
{
    public function __construct(
        private readonly FileCreatorService $fileCreator,  // ✅ Dépendance claire
    ) {}
}

Exemple complet : La testabilité en action

// Le service
class UserService
{
    public function __construct(
        private readonly UserRepository $repository,
        private readonly LoggerInterface $logger,
    ) {}
    
    public function findActiveUser(int $id): ?User
    {
        $this->logger->info('Searching for active user', ['id' => $id]);
        
        $user = $this->repository->findActive($id);
        
        if (!$user) {
            $this->logger->warning('Active user not found', ['id' => $id]);
            return null;
        }
        
        return $user;
    }
}

// Le test
class UserServiceTest extends TestCase
{
    public function test_findActiveUser_returns_user_when_exists(): void
    {
        // ✅ Toutes les dépendances sont mockables
        $repository = $this->createMock(UserRepository::class);
        $repository->method('findActive')->willReturn($user);
        
        $logger = $this->createMock(LoggerInterface::class);
        $logger->expects($this->once())->method('info');
        
        $service = new UserService($repository, $logger);
        $result = $service->findActiveUser(1);
        
        $this->assertSame($user, $result);
        
        // ✅ Aucune base de données réelle
        // ✅ Aucun fichier log réel
        // ✅ Test rapide, isolé, fiable
    }
}

Quand déroger aux principes ?

La philosophie de ce package est pragmatique :

Règle Peut-on déroger ? Exemple
Pas d'état interne ⚠️ Exception rare Cache interne avec TTL court
Pas de final ✅ Oui Classe utilitaire sans dépendances
Dépendre des interfaces ✅ Oui Value Objects, DTOs, Configs
Une capacité par service ✅ Oui OrderCalculatorService a 5 méthodes liées

Le critère ultime : Est-ce que mon code reste testable ?

Services disponibles

Service Description
ModelTransformableService Convertit les modèles Eloquent en Data DTOs typés

Voir la documentation complète

License

MIT © Andy Defer