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

jaumevallsvila's avatar

Paginate results from has many query

Hi everyone, I'm running into an issue on how to paginate results when query a has many relation, for instance I would like to display the results in a table using vue, paginating the has many collection.

Relations are as follow: Client has many campaigns Campaign belongs to a client

What I would like to achieve is paginate (5 results) the campaigns that belongs to a client for example:

Page 1: Client 1: camp 1 camp 2 camp 3 camp4 camp5 Page2: Client1: camp6 Client2: camp1 camp2 camp3 camp4 And so on.

I've tried the following but without success: Client::with([ 'campaign' => function ($query) { $query->limit(10) $query->orderBy('client_id'); }, ])->paginate(10)

Any advice

0 likes
19 replies
LaryAI's avatar
Level 58

To achieve the pagination of campaigns within each client, you can use Laravel's paginate method on the hasMany relationship. However, you need to handle this on a per-client basis. Here's how you can do it:

First, you'll need to get the clients and then paginate the campaigns for each client separately. You can't directly paginate across different clients' campaigns in a single query because pagination is designed to work on a single query result set.

Here's an example of how you can achieve this in your controller:

// Get the clients
$clients = Client::all();

// Iterate over each client and paginate their campaigns
foreach ($clients as $client) {
    $client->campaigns = $client->campaigns()->paginate(5);
}

// Pass the clients with their paginated campaigns to the view
return view('your_view', compact('clients'));

In your Vue component, you will need to handle the pagination for each client's campaigns. You can do this by creating a pagination component in Vue that takes the paginated campaigns for each client and allows you to navigate through the pages.

Here's a simplified example of what your Vue component might look like:

<template>
  <div>
    <div v-for="client in clients" :key="client.id">
      <h2>Client {{ client.id }}:</h2>
      <ul>
        <li v-for="campaign in client.campaigns.data" :key="campaign.id">
          {{ campaign.name }}
        </li>
      </ul>
      <pagination :data="client.campaigns" @page-changed="fetchCampaigns(client, $event)"></pagination>
    </div>
  </div>
</template>

<script>
export default {
  props: ['clients'],
  methods: {
    fetchCampaigns(client, page) {
      // Make an API call to fetch the campaigns for the client on the specified page
      // Update the client's campaigns with the new paginated data
    }
  }
}
</script>

In the fetchCampaigns method, you would make an API call to a route that returns the paginated campaigns for a specific client and page number. The server-side route would look something like this:

Route::get('/clients/{client}/campaigns', function (Client $client) {
    return $client->campaigns()->paginate(5);
});

Remember to handle the pagination links in your Vue component, as you'll need to provide a way for the user to navigate through the pages of campaigns for each client.

This approach allows you to paginate each client's campaigns independently, which seems to be what you're looking for based on your question.

jaseofspades88's avatar

I believe this is what you need..

$client = Client::find($id);//or route model binded

return $client->campaigns()->paginate(5);
jaumevallsvila's avatar

the issue with this method is that I would like to display the results in a table without fetching specific Client ID

tykus's avatar

@jaumevallsvila describe what/how you intend to interact with pagination links for an individual Client? What happens to all of the other displayed records whenever you request the next/previous page?

tykus's avatar

@jaumevallsvila that is implemented using the approach suggested by @snapey below, I.e the Client query is not paginated - it is eager-loaded - only the Campaigns are paginated. I would group the Collection of Campaigns by the client and use nested loops, but Snapey’s suggestion is equally valid

Just remember to order the campaigns by the Client ID (to group) and some other unique field (e.g. id) to ensure consistency of results

Snapey's avatar

I would get campaigns (paginated) and load client as a child record. (switch them around)

Then in the blade view iterate over the campaigns but pull the client out as a section heading. Omit the heading if it is the same as the one shown last.

$campaigns = Campaign::with('client')->paginate(5);

then in blade (very simplified)

@foreach($campaigns as $campaign)
	@if($lastClient ?? false != $campaign->client_id)
		<?php $lastClient = $campaign->client->id ?>
		<h2>{{ $campaign->client->name }} </h2>
    @endif
		<h3>{{ $campaign->title }}</h3>
		<!-- more details of campaign -->
 @endforeach

  {{ $campaigns->links() }}
amitsolanki24_'s avatar

@jaumevallsvila May be it will give you what you want

      $clients_with_campaigns = Client::with([
             'campaign'  => function ($query) {
                          $query->take(5);
              }
       ])->get();
jaumevallsvila's avatar

@tykus and @snapey Thank you very much for you replies, much appreciated.

Campaign::with('client')->orderBy('client_id')->orderBy('id')->paginate(5)

Vue view having the table structured like this

<table class="w-full relative text-sm text-left text-gray-500">
            <thead class="sticky top-0">
            <tr class="border-b bg-gray-50">
              <th class="px-4 py-3" scope="col">Name</th>
              <th class="px-4 py-3" scope="col">ID</th>
            </tr>
            </thead>
            <tbody>
            <template v-for="client in props.clients.data" :key="client.id">
              <tr v-if="lastClient ?? false !== client.client_id" class="border-b cursor-pointer bg-gray-50">
                <template :set="lastClient = client.client.id"></template>
                <th class="pl-[1rem] font-bold px-4 py-3 text-gray-900 whitespace-nowrap"
                >
                  {{ client.client.name }}
                </th>
                <th class="px-4 py-3">{{ client.client.id }}</th>

                </th>
              </tr>
              <tr>
                <th class="px-4 py-3" headers="baseline feature" scope="row">
                  {{ client.name }}
                </th>
                <td class="px-4 py-3">{{ client.id }}</td>
              </tr>
            </template>
            </tbody>
          </table>

However each client only shows one campaign.

tykus's avatar

@jaumevallsvila this is why I prefer the grouping and nested loop approach; it is easier to understand how the data is intended to be used

1 like
Snapey's avatar

@jaumevallsvila You should be looping over the campaigns and only showing the client info when it differs on the current campaign to the previous campaign.

jaumevallsvila's avatar

Hey guys,

Solved the issue with nested loops below the code for future reference

<template>
  <main>
    <div v-for="(groupedArray, id) in objectToLoopOn" :key="id">
      <pre v-for="array in groupedArray" :key="array.item">{{ array }}</pre>
      <hr />
    </div>
  </main>
</template>

<script>
const groupBy = (objectArray, property) => {
  return objectArray.reduce((acc, obj) => {
    const key = obj[property];
    const curGroup = acc[key] ?? [];

    return { ...acc, [key]: [...curGroup, obj] };
  }, {});
};

export default {
  data() {
    return {
      objectToLoopOn: groupBy(
        [
          { id: 11, item: "first one" },
          { id: 49, item: "tasty thing" },
          { id: 11, item: "amazing right?" },
          { id: 20, item: "cool cool" },
          { id: 49, item: "love watermelons" },
          { id: 83, item: "and you?" },
        ],
        "id"
      ),
    };
  },
};

Please or to participate in this conversation.