Un peu plus sur la couche de service en PHP

Dans la vie de chaque développeur, il arrive un moment où une compréhension des modèles et des règles populaires pour écrire du code propre commence à manquer. Cela se produit généralement lorsqu'un projet est soumis au flux qui est plus complexe qu'un site de catalogue classique. Lors de la création d'un tel projet, il est très important de définir la bonne architecture (surtout si le projet est à long terme), qui pourra s'adapter de manière aussi flexible et rapide aux nouvelles exigences de l'entreprise.





- ( service layer), ,   . MVC Laravel.





, , , .   , , -, , , .





, Service layer - . , .





, :





(Service layer) — , .





, . ,   ( ) -, . , S SOLID.





  , Eloquent , .. , , . , -, , . , .





Email

, - - , . .





namespace App\Http\Controllers;

use App\Http\Requests\CreateOrderRequest;
use Illuminate\Support\Facades\Mail;

class OrderController
{
    public function createOrder(CreateOrderRequest $request)
    {
        //   ...

        Mail::send('mail.order_created', [
            'order' => $order
        ], function ($message) use ($order) {
            $message->to($order->email)
                ->subject(trans('mail/order_created.mail_title'));
        });
    }
}
      
      



, . Laravel . , , .





public function editOrder(EditOrderRequest $request)
{
    //    ...

    Mail::send('mail.order_updated', [
        'order' => $order
    ], function ($message) use ($order) {
        $message->to($order->email)
            ->subject(trans('mail/order_updated.mail_title'));
    });
}
      
      



, , .





public function registerCustomer(RegisterCustomerRequest $request)
{
    //   ...

    Mail::send('mail.customer_register', [
        'customer' => $customer
    ], function ($message) use ($customer) {
        $message->to($customer->email)
            ->subject(trans('mail/customer_register.mail_title'));
    });
}
      
      



, Mail , , - .





, email . , , , .. - email , . .





, email , , - . , Mail . ( )? , . , , . , .





, NotificationService.





namespace App\Services;

use Illuminate\Support\Facades\Mail;
use App\Mail\Events\MailEventInterface;
use App\Mail\Events\OrderCreatedEvent;
use App\Mail\Events\OrderUpdatedEvent;
use App\Mail\Events\CustomerRegisterEvent;

class NotificationService
{
    public function notify(string $event, array $data)
    {
        $event = $this->makeNotificationEvent($event, $data);

        Mail::send($event->getView(), $event->getData(), function ($message) use ($event) {
            $message->to($event->getEmail())
                ->subject($event->getMailSubject());
        });
    }

    private function makeNotificationEvent(string $event, array $data) : MailEventInterface
    {
        switch ($event) {
            case 'order_created':
                return new OrderCreatedEvent($data);
            case 'order_updated':
                return new OrderUpdatedEvent($data);
            case 'customer_register':
                return new CustomerRegisterEvent($data);
            default:
                throw new \InvalidArgumentException("Undefined event $event");
        }
    }
}
      
      



,  MailEventInterface.





namespace App\Mail\Events;

interface MailEventInterface
{
    public function getView() : string;
    public function getData() : array;
    public function getEmail() : string;
    public function getMailSubject() : string;
}
      
      



, ,  OrderCreatedEvent ( ).





namespace App\Mail\Events;

class OrderCreatedEvent implements MailEventInterface
{
    private $order;

    public function __construct(array $data)
    {
        //   ( )

        $this->order = $data['order'];
    }

    public function getView(): string
    {
        return 'mail.order_created';
    }

    public function getData(): array
    {
        return [
            'order' => $this->order
        ];
    }

    public function getEmail(): string
    {
        return $this->order->email;
    }

    public function getMailSubject(): string
    {
        return trans('mail/order_created.mail_title');
    }
}
      
      



, .





namespace App\Http\Controllers;

use App\Http\Requests\CreateOrderRequest;
use App\Services\NotificationService;

class OrderController
{
    private $notificationService;
    
    public function __construct(NotificationService $notificationService)
    {
        $this->notificationService = $notificationService;
    }

    public function createOrder(CreateOrderRequest $request)
    {
        //   ...
        
        $this->notificationService->notify('order_created', [
            'order' => $order
        ]);
    }
}
      
      



? , . , , . ( -), , . , , " " . .





?

. . . , .   ?  , NotificationServiceInterface , -. - .





$this->app->when(OrderController::class)
    ->needs(NotificationServiceInterface::class)
    ->give(function () {
        return new ESputnikNotificationService();
    });

$this->app->when(OrderUpdateController::class)
    ->needs(NotificationServiceInterface::class)
    ->give(function () {
        return new MailNotificationService();
    });
      
      



, 95% , - .





?

, single responsibility , , , .





.





1. . , , try/catch.





class OrderController
{
    public function saveOrder(
        SaveOrderRequest $request, 
        OrderService $orderService, 
        NotificationService $notificationService
    ) {
        try {
            $order = $orderService->createOrderFromRequest($request);
            $notificationService->notify('order_created', [
                'order' => $order
            ]);

            return response()->json([
                'success' => true,
                'data' => [
                    'order' => $order
                ]
            ]);
        }
        catch (OrderServiceException|NotificationServiceException $e) {
            return response()->json([
                'success' => false,
                'exception' => $e->getMessage()
            ]);
        }
    }
}
      
      



2. , . , Operation (CreateOrderOperation). try/catch, OperationResult, . .





class OrderController
{
    public function saveOrder(
        SaveOrderRequest $request,
        CreateOrderOperation $createOrderOperation
    ) {
        //         ..
        $result = $createOrderOperation->createOrderFromRequest($request);

        //    ,  OperationResult
        //   JsonSerializable

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



UPD: , , . , , ..Service.





UPD: Et bien sûr, il n'est pas tout à fait correct de transférer des données supplémentaires vers la couche service sous la forme d'une requête complète. Il serait préférable de transmettre un DTO valide. Vous devez également renvoyer quelque chose de compréhensible à partir des services. Cette approche a du sens au moins dans l'écosystème Laravel.





À ce stade, l'article est arrivé à sa conclusion logique. J'espère que cela aidera les développeurs novices et ceux qui ne sont pas familiers avec la couche de service à comprendre pleinement l'essence de l'approche et les problèmes qu'elle résout.





Merci à tous pour votre attention!








All Articles