Courses

Laravel 11: Small Reservation Project Step-By-Step

Guide: Export PDF

In the last lesson, we will allow users with the guide role to export activities into a PDF file. The PDF will show the activity name, start time, and a list of participants in a table.


Exporting to PDF

We will use the package barryvdh/laravel-dompdf for exporting PDF. It's a Laravel wrapper for the DOMPDF package.

composer require barryvdh/laravel-dompdf

Next, we need a Route and a button below the activity.

routes/web.php:

// ...
 
Route::middleware('auth')->group(function () {
Route::get('/activities', [MyActivityController::class, 'show'])->name('my-activity.show');
Route::get('/guides/activities', [GuideActivityController::class, 'show'])->name('guide-activity.show');
Route::get('/guides/activities/{activity}/pdf', [GuideActivityController::class, 'export'])->name('guide-activity.export');
Route::delete('/activities/{activity}', [MyActivityController::class, 'destroy'])->name('my-activity.destroy');
 
// ...
});

resources/views/activities/guide-activities.blade.php:

// ...
<div class="space-y-3">
<a href="{{ route('activity.show', $activity) }}">
<img src="{{ asset($activity->thumbnail) }}" alt="{{ $activity->name }}"> </a>
<h2>
<a href="{{ route('activity.show', $activity) }}" class="text-lg font-semibold">{{ $activity->name }}</a>
</h2>
<time>{{ $activity->start_time }}</time>
<a href="{{ route('guide-activity.export', $activity) }}" class="inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition ease-in-out duration-150">
Download PDF
</a>
</div>
// ...

Now we can use the package to download the PDF.

app/Http/Controllers/GuideActivityController.php:

use Barryvdh\DomPDF\Facade\Pdf;
 
class GuideActivityController extends Controller
{
// ...
 
public function export(Activity $activity)
{
abort_if(auth()->user()->role_id !== Role::GUIDE->value, Response::HTTP_FORBIDDEN);
 
$data = $activity->load(['participants' => function($query) {
$query->orderByPivot('created_at');
}]);
 
return Pdf::loadView('activities.pdf', ['data' => $data])->download("{$activity->name}.pdf");
}
}

We are using Blade for outputting data.

resouces/views/activities/pdf.blade.php:

<h1>{{ $data->name }}</h1>
 
<h4>Start time {{ $data->start_time }}</h4>
 
<table>
<thead>
<tr>
<th></th>
<th>Name</th>
<th>Email</th>
<th>Registration Time</th>
</tr>
</thead>
<tbody>
@foreach($data->participants as $participant)
<tr>
<td>{{ $loop->iteration }}</td>
<td>{{ $participant->name }}</td>
<td>{{ $participant->email }}</td>
<td>{{ $participant->pivot->created_at }}</td>
</tr>
@endforeach
</tbody>
</table>

Of course, you might need to add some styles to the PDF file for better readability.


Tests

There isn't much that we can test with the PDF. We will add one test to the GuideActivityTest to check if PDF isn't empty, the content type is application/pdf, and the filename is correct.

tests/Feature/GuideActivityTest.php:

class GuideActivityTest extends TestCase
{
// ...
 
public function test_pdf_export()
{
$guide = User::factory()->guide()->create();
$activity = Activity::factory()->create(['guide_id' => $guide->id]);
 
$response = $this->actingAs($guide)->get(route('guide-activity.export', $activity));
 
$this->assertNotEmpty($response->getContent());
$this->assertEquals('application/pdf', $response->headers->get('Content-Type'));
$this->assertEquals('attachment; filename="' . $activity->name .'.pdf"', $response->headers->get('Content-Disposition'));
}
}
avatar

Tried to get my version of your program to work but temporality gave up and decided to try and work with yours to figure out what I am doing wrong. Having after I downloaded your GitHub repository and set it up which went ok. I try to delete a company and get the following Error.


SQLSTATE[23000]: Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails (`lvdy_reservations`.`activities`, CONSTRAINT `activities_company_id_foreign` FOREIGN KEY (`company_id`) REFERENCES `companies` (`id`))

delete from `companies` where `id` = 4

public function destroy(Company $company)

    {

        $company->delete();



        return to_route('companies.index');

    }

How can I fix this or better yeat sence it is your code can you?

Ok yes if you delete the activities first then you can delete the company so I thank we need to disable the company delete if there are any activities available what do you thank.

avatar

cascadeOnDelete() should work

avatar
You can use Markdown
avatar

I'm getting an error on the test test_pdf_export, any ideas? I think it has something to do with the formatting, but not sure.

Failed asserting that two strings are equal.
  -'attachment; filename="quasi.pdf"'
  +'attachment; filename=quasi.pdf'
avatar

Message is very clear. You expect some value but get a different one

avatar

For anyone else who runs into the failing test. Update this line from the above test:

$this->assertEquals('attachment; filename="' . $activity->name .'.pdf"', $response->headers->get('Content-Disposition'));

to this:

$this->assertEquals('attachment; filename='.$activity->name.'.pdf', $response->headers->get('Content-Disposition'));
avatar
You can use Markdown
avatar
You can use Markdown