Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

boldstar's avatar

How to change format of response data?

Hello, I am wanting to change the format of a response that I am getting when I do the following in my controller

public function questionindex($client_id)
    {
        $engagements = Engagement::where('client_id', $client_id)->with('questions')->get();
    
        foreach($engagements as $engagement) {
            echo($engagement->questions);
        }

        return response($engagements);
    }

Currently the response is in a unusable format. I would like for it to be a single array of objects. Any suggestions on helpers I can use to make this happen?

here is what the I currently get back

[{"id":32,"engagement_id":63,"question":"Just testing out the workflow?","answer":null,"answered":0,"created_at":"2018-10-05 21:33:08"}][{"id":63,"client_id":10,"return_type":"1040","year":"2018","assigned_to":"Lissa","status":"Recieved","done":0,"created_at":"2018-10-05 21:32:47","updated_at":"2018-10-05 21:32:47","questions":[{"id":32,"engagement_id":63,"question":"Just testing out the workflow?","answer":null,"answered":0,"created_at":"2018-10-05 21:33:08"}]}]

This is what I would like it to be

[
    {
        "id":32,
        "engagement_id":63,
        "question":"Just testing out the workflow?",
        "answer":null,
        "answered":0,
        "created_at":"2018-10-05 21:33:08"
    }

    {
        "id":32,
        "engagement_id":63,
        "question":"Just testing out the workflow?",
        "answer":null,
        "answered":0,
        "created_at":"2018-10-05 21:33:08"
    }

    {
        "id":32,
        "engagement_id":63,
        "question":"Just testing out the workflow?",
        "answer":null,
        "answered":0,
        "created_at":"2018-10-05 21:33:08"
    }
]
0 likes
27 replies
Snapey's avatar

If you don't want nested data then you need to use a join.

but, be prepared to see duplicate engagements if there can be multiple questions.

is it a 1:1 relationship or 1:many ?

D9705996's avatar

Why are you echoing out questions in the for loop as this doesn't affect the response? This should give you what you want

   public function questionindex($client_id)
    {
        return Engagement::where('client_id', $client_id)->with('questions')->get();
   }
boldstar's avatar

@D9705996, I am using echo because I ONLY want the questions. Perhaps there is a better method for doing that?

@Snapey, 1:many. I use the client_id to know which list of engagements to get, but from the engagements I only want the questions. I dont actually need the engagements data.

  1. Clients hasMany Engagements
  2. Engagements hasMany Questions

No direct relationship between client and questions....

Snapey's avatar

echo is not your friend.

You only want the questions, but for which engagement? Can a client have multiple engagements?

Snapey's avatar
public function questionindex($client_id)
    {
        $questions = Client::find($client_id)
                            ->engagement()
                            ->questions()
                            ->get();

        return $questions;
    }
boldstar's avatar

@Snapey, yes. It works fine retrieving the engagements and questions, however I don't need the engagements data for this particular instance so reducing the response to just the questions is my ideal goal...

Do I need to update my Client model to support ->questions()? currently I only have a relationship defined on my Engagement model...

Snapey's avatar

My last post only gets the questions.

You could avoid mentioning engagement() by setting up a hasManyThrough relationship but its not necessary for the sake of just adding it to the query

it will work as shown as long as Client has an engagement method, and Engagement has a questions method

boldstar's avatar

Okay well I don't think that I do. Here is the model for my Client

class Client extends Model
{
    protected $fillable = 
    [
        'category',
        'referral_type', 
        'first_name', 
        'middle_initial', 
        'last_name', 
        'occupation', 
        'dob', 
        'email', 
        'cell_phone', 
        'work_phone',
        'spouse_first_name', 
        'spouse_middle_initial', 
        'spouse_last_name', 
        'spouse_occupation', 
        'spouse_dob', 
        'spouse_email', 
        'spouse_cell_phone', 
        'spouse_work_phone', 
        'street_address',
        'city',
        'state',
        'postal_code', 
    ];

    protected $hidden = ['created_at', 'updated_at'];

    public function engagements()
    {
        return $this->hasMany('App\Engagement');
    }

}

and here is the Engagement model

class Engagement extends Model
{
    protected $fillable =
    [
        'client_id',
        'return_type',
        'year',
        'assigned_to',
        'status',
    ];

    public function questions()
    {
        return $this->hasMany('App\Question');
    }

}

And on the controller I have only imported use App\Engagement can I import use App\Client as well?

D9705996's avatar

You just need to change ->engagement() in the query to use ->engagements() to match your method name in the client model

D9705996's avatar

Jusf remember you should define the inverse relationships as well on your models E.g. a engagement belongs to a client so on the engagement model

public function client() {
  return $this->belongsTo(Client::class);
}
boldstar's avatar

@D9705996 , I have the inverse defined I just removed it for the sake of this discussion. However changing the method to engagements() so it matches my Model will return this error

BadMethodCallException: Call to undefined method Illuminate\Database\Eloquent\Relations\HasMany::questions() in file C:\laragon\www\traxit\vendor\laravel\framework\src\Illuminate\Support\Traits\ForwardsCalls.php on line 50
Stack trace:
D9705996's avatar

So did you change your query to


Client::find($client_id)
   ->engagements() // add an s
   ->questions()
   ->get();

I thought the query relationships had to match the function name on you model. If it worked with engagement() then I must have misunderstood something.

boldstar's avatar

@D9705996, no you are correct I have changed it to engagements() and the alarm is from the questions() method. I miss read the alarm.

Client::find($client_id)
   ->engagements()
   ->questions() // error is here
   ->get();

Which also doesn't make sense because I have questions() defined in my Engagement model...

boldstar's avatar

@D9705996 , no it gave me the alarm for definingengagement() instead of engagements()

Snapey's avatar

You want all questions of the client even though they belong to many engagements?

boldstar's avatar

@Snapey, yes I guess that's not conventional? without diving to deep into my frontend I am using the Client Id to find the engagements. So I have a view where I am showing questions for the client... Is there not away to constrain the response to only show the nested data?

@D9705996 , I tried it out. Even tried out the has method to, and it would only return the engagement. the whereHas would alarm out on me. Because the Question:: class is not defined on the Model that knows the client_id...

boldstar's avatar

using this method

public function questionindex($client_id)
    {
        $engagements = Engagement::where('client_id', $client_id)->with('questions')->get();

        return response($engagements);
    }

It will give me a response like this

[
    {
        "id": 63,
        "client_id": 10,
        "return_type": "1040",
        "year": "2018",
        "assigned_to": "Lissa",
        "status": "Recieved",
        "done": 0,
        "created_at": "2018-10-05 21:32:47",
        "updated_at": "2018-10-05 21:32:47",
        "questions": [
            {
                "id": 32,
                "engagement_id": 63,
                "question": "Just testing out the workflow?",
                "answer": null,
                "answered": 0,
                "created_at": "2018-10-05 21:33:08"
            }
        {
                "id": 32,
                "engagement_id": 63,
                "question": "Just testing out the workflow?",
                "answer": null,
                "answered": 0,
                "created_at": "2018-10-05 21:33:08"
            }
        ]
    },

    {
        "id": 63,
        "client_id": 10,
        "return_type": "1040",
        "year": "2018",
        "assigned_to": "Lissa",
        "status": "Recieved",
        "done": 0,
        "created_at": "2018-10-05 21:32:47",
        "updated_at": "2018-10-05 21:32:47",
        "questions": [
            {
                "id": 32,
                "engagement_id": 63,
                "question": "Just testing out the workflow?",
                "answer": null,
                "answered": 0,
                "created_at": "2018-10-05 21:33:08"
            }
        ]
    }
]

is there not a foreach method that will tell it foreach engagement only show questions?

Snapey's avatar

Im struggling to get a straight answer here, I gues i'll just have to keep throwing out solutions until something happens to be right?

$engagements = Client::find($client_id)
            ->engagements()->pluck('id');

$questions = Questions::whereIn('engagement_id', $engagements)->get();

return $questions;

Cronix's avatar

is there not a foreach method that will tell it foreach engagement only show questions?

@foreach ($engagements as $engagement)
    @foreach($engagement->questions as $question)
        {{ $question->question }}
    @endforeach 
@endforeach
D9705996's avatar

Drat. I suppose you could do this to get all questions for all engagements for a client and if it works could be refactored.

$questions = collect();

foreach(Client::find($client_id)->engagements as $engagement) {
  foreach($engagement->questions as $question) {
    $questions->push($question);
  }
}
return $questions;
boldstar's avatar
boldstar
OP
Best Answer
Level 2

I used this

public function questionindex($client_id)
    {
        $engagements = Engagement::where('client_id', $client_id)->with('questions')->get();

        $questions = $engagements->pluck('questions');

        $flatten = $questions->flatten(1);

        return response($flatten);
    }

it returns a response of questions only like so only belonging to client

[
    {
        "id": 28,
        "engagement_id": 57,
        "question": "This is a new question??",
        "answer": "<p>Why isnt this working?</p>",
        "answered": 1,
        "created_at": "2018-10-05 18:48:48"
    },
    {
        "id": 36,
        "engagement_id": 57,
        "question": "What happens if add a new questions?",
        "answer": "<p>But this doesnot work</p>",
        "answered": 1,
        "created_at": "2018-10-06 14:45:29"
    },
    {
        "id": 37,
        "engagement_id": 65,
        "question": "Just adding a new question?",
        "answer": "<p>So does this work now to?</p>",
        "answered": 1,
        "created_at": "2018-10-06 14:50:10"
    }
]

One array of objects.

D9705996's avatar

You can inline some temporary variables

public function questionindex($client_id) {
  return Engagement::where('client_id', $client_id)
     ->with('questions')
     ->get()
     ->pluck('questions')
     ->flatten(1);
}

Should give the same output

Cronix's avatar

You actually don't need to use get() if you use pluck(). pluck() will perform the get(). Whoohoo saved a line of code lol.

Snapey's avatar

did you try my whereIn solution?

Please or to participate in this conversation.