'Laravel API resourceCollection using array rather than model

I have an API that uses API resource and resource collections to correctly format the JSON responses. In order to decouple my controller from my model I use an adapter to query the underlying model. I'd like to pass the adapter return values as arrays, rather than Eloquent models, to ensure that any furture adapters are easier to right in respect to their return data structures. To create the array return values I serialise my adapter Eloquent results with ->toArray().

I have 2 API Resources to correctly format these results, for a single resource I have:

use Illuminate\Http\Resources\Json\Resource;

class Todo extends Resource
{
    /**
     * Transform the resource into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return $this->resource;
    }
}

For a resource collection I have:

use Illuminate\Http\Resources\Json\ResourceCollection;

class TodoCollection extends ResourceCollection
{
    /**
     * Transform the resource collection into an array.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return array
     */
    public function toArray($request)
    {
        return [
            'data' => $this->collection
                          ->map
                          ->toArray($request)
                          ->all()
        ];
    }
}

When I return a single resource from my controller with :

use App\Http\Resources\Todo;

public function show($id)
{
    return new Todo($this->todoAdapter->findById($id));
}

and the adapter query as:

public function findById(int $id){
        return TodoModel::findOrFail($id)
                ->toArray();
}

This works as expected. The problem comes when I try to pass an array of a collection of models i.e.

public function index(Request $request)
{
    $todos = $this->todoAdapter->getAllForUserId(Auth::id(), 'created_by', 'desc', self::DEFAULT_PAGINATE);
    return new TodoCollection($todos);
}

and the adapter query as:

public function getAllForUserId(int $userId, string $sortField, string $sortDir, int $pageSize = self::DEFAULT_PAGINATE)
{
        return Todo::BelongsUser($userId)
                                    ->orderBy($sortField, $sortDir)
                                    ->paginate($pageSize)
                                    ->toArray();
}

I get the following error:

"message": "Call to a member function first() on array",
    "exception": "Symfony\\Component\\Debug\\Exception\\FatalThrowableError",
    "file": "/home/vagrant/code/public/umotif/vendor/laravel/framework/src/Illuminate/Http/Resources/CollectsResources.php",
    "line": 24,

I'm guessing that I can't do 'new TodoCollection($todos)' where $todos is an array of results. How would I get my todoCollection to work with arrays? Any suggestions would be much appreciated!



Solution 1:[1]

Your collections toArray is trying to do too much:

$this->collection
         ->map
         ->toArray($request)
         ->all()

Just directly call $this->collection->toArray().

Solution 2:[2]

Just to update this. In the end I found that creating a collection from the array of results and passing that to the resource collection constructor worked, though I did have to add explicit mappings within the resource collection for links and meta etc.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 InTooDeep