Laravel - Comment injecter des services dans les API Resources

Depuis sa version 5.5, Laravel fournit une façon simple d'exposer ses models Eloquent au format JSON, mais cette fonctionnalités a quelques défauts, notamment au niveau de l'injection de dépendance. Nous allons voir comment contourner ce défaut.

Introduction

Depuis sa version 5.5, Laravel fournit un moyen simple d’exposer les modèles Eloquent au format JSON https://laravel.com/docs/5.5/eloquent-resources

Cette fonctionnalité est très utile, mais lorsque l’on souhaite faire des choses un peu complexes, à savoir injecter des services au sein de ses resources, on se rend vite compte que cela n’a pas été pensé pour.

Ma solution est de créer une Factory qui va s’occuper de créer la resource et d’y injecter les services voulus.

Création de la Factory

Nous allons créer le fichier app/Http/Resources/ResourceFactory.php

<?php

namespace App\Http\Resources;

use Illuminate\Contracts\Pagination\Paginator;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use App\Models\Model;

class ResourceFactory
{
    private $service;

    public function __construct($service)
    {
        $this->service = $service;
    }

    /**
     * @param Model|Collection|Paginator $data
     * @param string $modelClass
     * @return Resource
     * @throws \Exception
     */
    public function getResource($data, $modelClass)
    {
        $resourceClass = $this->getResourceClass($modelClass);
        switch ($modelClass) {
            case Model::class:
                $resourceClass::setService($this->service);
                break;
        }

        if ($data instanceof Model) {
            return new $resourceClass($data);
        }

        if ($data instanceof Collection || $data instanceof Paginator) {
            return $resourceClass::collection($data);
        }

        throw new \Exception('Unable to create resource from model');
    }

    /**
     * @param string $modelClass
     * @return string
     * @throws \Exception
     */
    private function getResourceClass($modelClass)
    {
        $explode = explode('\\', $modelClass);
        $resourceClass = 'App\\Http\\Resources\\' . last($explode) . 'Resource';
        if (!class_exists($resourceClass)) {
            throw new \Exception('Unable to find resource from model class ' . $modelClass);
        }

        return $resourceClass;
    }
}

Création de la resource

Ensuite, nous allons créer la resource qui à besoin du service. Créez le fichier app/Http/Resources/ModelResource.php

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\Resource;

class ModelResource extends Resource
{
    protected static $service;

    public static function setService($service)
    {
        self::$service = $service;
    }

    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request
     * @return array
     */
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'custom_value' => self::$service->getCustomValue(),
        ];
    }
}

Utilisation de la Factory

Finalement, nous allons utiliser notre Factory dans notre controller :

<?php

namespace App\Http\Controllers;
use App\Http\Resources\ResourceFactory;
use App\Models\Model;
class ModelController extends Controller
{
    public function index(ResourceFactory $resourceFactory)
    {
        return $resourceFactory->getResource(Model::all(), Model::class);
    }
}

Conclusion

De cette façon, nous pouvons injecter n’importe quel service via ma ResourceFactory et nous n'avons plus qu'une seule façon de créer nos resources.

N’hésitez pas à me contacter pour me dire ce que vous pensez de cette solution, peut-être en avez-vous trouvé une meilleure, auquel cas je suis preneur :)