1

I been trying to figure this out for some time now. Basically i got 2 models ' Recipe ', ' Ingredient ' and one Controller ' RecipeController ' . I'm using Postman to test my API. When i go to my get route which uses RecipeController@getRecipe, the return value is as per the pic below:

Return for Get Route

If i want the return value of the get route to be in the FORMAT of the below pic, how do i achieve this? By this i mean i don't want to see for the recipes: the created_at column, updated_at column and for ingredients: the pivot information column, only want name and amount column information.

Return Value Format I Want

Recipe model:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Recipe extends Model
{
    protected $fillable = ['name', 'description'];

    public function ingredients()
    {
      return $this->belongsToMany(Ingredient::class, 
      'ingredient_recipes')->select(array('name', 'amount'));
    }
}

Ingredient Model:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Ingredient extends Model
{
    protected $fillable = ['name', 'amount'];
}

RecipeController

<?php

namespace App\Http\Controllers;


use App\Ingredient;
use App\Recipe;
use Illuminate\Http\Request;

class RecipeController extends Controller {

public function postRecipe(Request $request)
{
    $recipe = new Recipe();
    $recipe->name = $request->input('name');
    $recipe->description = $request->input('description');
    $recipe->save();

    $array_ingredients = $request->input('ingredients');
    foreach ($array_ingredients as $array_ingredient) {
        $ingredient = new Ingredient();
        $ingredient->name = $array_ingredient['ingredient_name'];
        $ingredient->amount = $array_ingredient['ingredient_amount'];
        $ingredient->save();
        $recipe->ingredients()->attach($ingredient->id);
    }

    return response()->json(['recipe' => $recipe . $ingredient], 201);
}

public function getRecipe()
{
    $recipes = Recipe::all();
    foreach ($recipes as $recipe) {
        $recipe = $recipe->ingredients;
    }
    $response = [
        'recipes' => $recipes
    ];
    return response()->json($response, 200);
}

API Routes:

Route::post('/recipe', 'RecipeController@postRecipe')->name('get_recipe');
Route::get('/recipe', 'RecipeController@getRecipe')->name('post_recipe');

Thanks Guys!

John Biggs
  • 53
  • 1
  • 8

1 Answers1

1

I think your best solution is using Transformer. Using your current implementation what I would recommend is fetching only the needed field in your loop, i.e:

foreach ($recipes as $recipe) {
    $recipe = $recipe->ingredients->only(['ingredient_name', 'ingredient_amount']);
}

While the above might work, yet there is an issue with your current implementation because there will be tons of iteration/loop polling the database, I would recommend eager loading the relation instead. But for the sake of this question, you only need Transformer.

Install transformer using composer composer require league/fractal Then you can create a directory called Transformers under the app directory.

Then create a class called RecipesTransformer, and initialize with:

namespace App\Transformers;

use App\Recipe;

use League\Fractal\TransformerAbstract;

class RecipesTransformer extends TransformerAbstract
{
    public function transform(Recipe $recipe)
    {
        return [
            'name' => $recipe->name,
            'description' => $recipe->description,
            'ingredients' => 
                $recipe->ingredients->get(['ingredient_name', 'ingredient_amount'])->toArray()
        ];
    }
}

Then you can use this transformer in your controller method like this:

use App\Transformers\RecipesTransformer;
......
public function getRecipe()
{
     return $this->collection(Recipe::all(), new RecipesTransformer);
     //or if you need to get one
     return $this->item(Recipe::first(), new RecipesTransformer);
}

You can refer to a good tutorial like this for more inspiration, or simply go to Fractal's page for details.

Update

In order to get Fractal collection working since the example I gave would work if you have Dingo API in your project, you can manually create it this way:

public function getRecipe()
{
    $fractal = app()->make('League\Fractal\Manager');
    $resource = new \League\Fractal\Resource\Collection(Recipe::all(), new RecipesTransformer);

     return response()->json(
        $fractal->createData($resource)->toArray());
}

In case you want to make an Item instead of collection, then you can have new \League\Fractal\Resource\Item instead. I would recommend you either have Dingo API installed or you can follow this simple tutorial in order to have in more handled neatly without unnecessary repeatition

  • Hey Omisakin, Thanks for the reply. I've require league/fractal, created the class and implemented the ' return $this->collection(Recipe::all(), new RecipesTransformer); ' into my controller @getRecipe. The problem i i get now is: ' Method [collection] does not exist. ' . The method ' collection ' is not found in recipe controller. – John Biggs Jul 24 '17 at 10:53
  • Thx again for the reply Omisakin. I will look into the Dingo API, but for now I have implemented your update to my controller and now receive ' array_key_exists(): The first argument should be either a string or an integer ' . ' . How do i solve this? – John Biggs Jul 24 '17 at 13:12
  • Could you check the stack trace of where the issue is coming from. I guess it might be from the Transformer's transform function. so comment out this and test again: `'ingredients' => $recipe->ingredients->get(['ingredient_name', 'ingredient_amount'])->toArray()` – Oluwatobi Samuel Omisakin Jul 24 '17 at 14:07
  • Cool, I commented what you said, and it displayed the name and description properties, except that there is no ingredients now. How i get the ingredients to work :P – John Biggs Jul 24 '17 at 14:22
  • i tested something, when i add ' () ' to ingredients ie: ' $recipe->ingredients()->get(['name', 'amount'])->toArray() ' , i do not see the other columns in the ingredient table like id, created_at, updated_at which is what i want, but i see the property in the ingredient array ' "pivot": { "recipe_id": 1, "ingredient_id": 2 } which i do not want to see. How i get rid of the pivot details? – John Biggs Jul 24 '17 at 14:58
  • Add it to your model as hidden `protected $hidden = ['pivot'];` – Oluwatobi Samuel Omisakin Jul 24 '17 at 15:33
  • 1
    Thank you so much Omisakin!!! That solved it !!! Added it to the ' Ingredient ' Model and no issue anymore. You were with me till the end. Again thx bro :) – John Biggs Jul 24 '17 at 15:57
  • Glad its solved @JohnBiggs but I guess there is still many calls to the database, perhaps that line `'ingredients' => $recipe->ingredients->get(['ingredient_name', 'ingredient_amount'])->toArray()` could turn to `'ingredients' => $recipe->ingredients->only(['ingredient_name', 'ingredient_amount'])->toArray()` since its seems `ingredients` has been resolved to a collection. – Oluwatobi Samuel Omisakin Jul 24 '17 at 20:31