0

We are currently developing a feature in codotto.com where a user can comment on an IT meetup. Each comment can have an answer to it. We are only allowing for one-level deep answers, so something like:

- Comment 1
  - Answer to comment 1
  - Answer to comment 1
- Comment 2
  - Answer to comment 2
  - Answer to comment 2

I have the following database structure:

// meetup_messages
- id
- user_id
- meetup_id
- meetup_message_id (nullable) -> comments that do not answer will have this set to nullable

In my model I define the answers as a HasMany relationship:

class MeetupMessage extends Model
{
  // ...

  public function answers(): HasMany
  {
      return $this->hasMany(self::class, 'meetup_message_id');
  }
}

Then on my controller, I get all comments that do not have answers:


public function index(
        IndexMeetupMessageRequest $request,
        Meetup $meetup,
        MeetupMessageService $meetupMessageService
    ): MeetupMessageCollection
    {
        $meetupMessages = MeetupMessage::with([
            'user',
            // 'answers' => function ($query) {
            //   $query->limit(3);
            // }
            'answers'
        ])
            ->whereNull('meetup_message_id')
            ->whereMeetupId($meetup->id)
            ->paginate();

        return new MeetupMessageCollection($meetupMessages);
    }

Then on my MeetupMessageCollection:

class MeetupMessageCollection extends ResourceCollection
{
    public function toArray($request)
    {
        return parent::toArray($request);
    }
}

Then on my MeetupMessageResource:

<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Collection;

class MeetupMessageResource extends JsonResource
{
    public function toArray($request)
    {
        return collect([
            // 'answers' => new MeetupMessageCollection($this->whenLoaded('answers')),
        ])
            ->when(
                is_null($this->meetup_message_id) && $this->relationLoaded('answers'),
                function (Collection $collection) {
                    $collection->put('answers', MeetupMessageCollection::collection($this->answers));
                }
            );
    }
}

But I get the following error: Call to undefined method App\\Models\\Meetup\\MeetupMessage::mapInto(). How can I still use MeetupMessageCollection by passing the answers to it?

Bruno Francisco
  • 3,841
  • 4
  • 31
  • 61
  • 1
    @Sumitkumar thank you for the comment but this comment is completely out of question. I'm not asking for changes in DB schema – Bruno Francisco Oct 22 '22 at 20:29
  • You are mixing stuff, you cannot use a normal resource collection into a resource... the [documentation](https://laravel.com/docs/9.x/eloquent-resources) is pretty self explanatory, what don't you understand about it? – matiaslauriti Oct 23 '22 at 00:34
  • @matiaslauriti never got in the docs where you can't use a resource inside a resource. I appreciate you for pointing it out. I have moved the logic into private methods to maintain consistency. – Bruno Francisco Oct 24 '22 at 13:00
  • sorry I was not exactly of help. I am very confused of what you want to achieve (based on the code you are mixing in there). Where you able to fix the issue? – matiaslauriti Oct 24 '22 at 16:54
  • @matiaslauriti I have added an answer to the question. Please, feel free to add your own answer after reading mine and I will accept yours. I left an answer with my question in case someone would like to get more details on how to solve this – Bruno Francisco Oct 30 '22 at 13:41

1 Answers1

0

As @matialauriti pointed out, you cant use resource collections inside collections in Laravel

class MeetupMessageResource extends JsonResource
{
  public function toArray()
  {
    return [
      'answers' => new MeetupMessageCollction($this->answers) // ❌ You can't do this
    ]
  }
}

My solution was to pull my resource formation to a private method and re-use it if answers is present:

class MeetupMessageResource extends JsonResource
{
  public function toArray($request)
  {
      return collect($this->messageToArray($this->resource))
          ->when($this->relationLoaded('user'), function (Collection $collection) {
              $collection->put('user', $this->userToArray($this->user));
          })
          // ✅ Now I don't need to use Resources inside my API Resource class
          ->when(
              is_null($this->meetup_message_id) && $this->relationLoaded('answers'),
              function (Collection $collection) {
                  $answers = $this
                      ->answers
                      ->map(function (MeetupMessage $answer) {
                          return array_merge(
                              $this->messageToArray($answer),
                              ['user' => $this->userToArray($answer->user)]
                          );
                      });
                  $collection->put('answers', $answers);
              }
          );
  }

  private function messageToArray(MeetupMessage $meetupMessage): array
  {
      return [
          'id' => $meetupMessage->id,
          'message' => Purify::config(MeetupMessageService::CONFIG_PURIFY)->clean($meetupMessage->message),
          'answersCount' => $this->whenCounted('answers'),
          'createdAt' => $meetupMessage->created_at,
      ];
  }
}
matiaslauriti
  • 7,065
  • 4
  • 31
  • 43
Bruno Francisco
  • 3,841
  • 4
  • 31
  • 61