andydefer / php-services
Collection of reusable services for PHP and Laravel applications including model transformation, caching, and common business logic
Package info
github.com/andydefer/php-services
Language:Makefile
pkg:composer/andydefer/php-services
0.1.1
2026-06-06 19:11 UTC
Requires
- php: ^8.2
- andydefer/domain-structures: ^1.16
- andydefer/php-vo: ^0.5.1
- laravel/framework: ^12.0|^13.0|^14.0|^15.0
Requires (Dev)
- barryvdh/laravel-ide-helper: ^3.6
- composer/composer: ^2.0
- larastan/larastan: ^3.8
- laravel/pint: ^1.26
- orchestra/testbench: ^10.8
- phpunit/phpunit: ^12.5
- rector/rector: *
- symfony/var-dumper: ^7.0
- vimeo/psalm: ^6.14
README
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