Edit PDF Form fields with PHP

Intro

Today I’m going to present a use case of a client who requires a PHP application that can export PDF files filled with  collected data.

To illustrate, I will present and small example project in Laravel.

Setting environment up

Let’s start creating a Laravel project

composer create-project laravel/laravel PDFTest

The next step is create a docker container for this project.

version: '3.6'

services:

  webserver:
    image: nginx:latest
    container_name: pdf_web
    env_file: ./docker/environment.env
    volumes:
      - ./:/code
      - ./docker/nginx/site.conf:/etc/nginx/conf.d/site.conf
      - ./docker/nginx/laravel.site.conf:/etc/nginx/conf.d/api.conf
      - ./docker/nginx/fastcgi.conf:/etc/nginx/fastcgi.conf
    ports:
      - "80:80"
      - "8888:80"

  php:
    image: erdiko/php-fpm:latest
    container_name: pdf_php
    env_file: ./docker/environment.env
    volumes:
      - ./:/code
    ports:
      - "9000:9000"

PDF library

After some research, I opted for pdftk, since it seems to be the most widely used.

Pdftk is a cross-platform binary that provides a bunch of handy command line options to manipulate PDFs like get plain text content, list fields, list fields data, fill in forms, etc.

A simple entrypoint.sh script is used to install the pdftk.

#!/bin/bash

apt update && apt install -y pdftk;

php-fpm

that has to vi mapped and executed in the php services, getting something like this

php:
 image: erdiko/php-fpm:latest
 container_name: pdf_php
 env_file: ./docker/environment.env
 volumes:
 - ./:/code
 - ./docker/entrypoint.sh:/usr/local/etc/php/entrypoint.sh
 ports:
 - "9000:9000"
 entrypoint: /usr/local/etc/php/entrypoint.sh

How to integrate in PHP

Here we will install php-pdftk package. Php-pdftk is a php package that relays on pdftk to manipulate PDF documents from php applications.

composer require mikehaertl/php-pdft

that provides a PHP class, named PDF, with all needed methods to work with PDF files.

Let’s put hands on the code

The first step is create controller, which I named it PdfEditor.

php artisan make:controller PdfEditor

Below is how the implementation looks like,

<?php

namespace App\Http\Controllers;

use mikehaertl\pdftk\Pdf;

class PdfEditor extends Controller
 {
 /**
 * Show the application dashboard.
 *
 * @return \Illuminate\Http\Response
 */
 public function index()
 {
 return view('pdftest.upload');
 }

public function store(Request $request)
 {
 $uploadedFile = $request->file('pdf_file');
 $filename = $uploadedFile->getClientOriginalName();

$request->session()->put('current_pdf', $filename);

Storage::disk('local')->putFileAs(
 'files/'.$filename,
 $uploadedFile,
 $filename
 );

return redirect()->back()->with('success', 'Your file is submitted Successfully');
 }

public function preview()
 {
 if(Session::has('current_pdf')) {
 $filepath = Storage::disk('local')->getDriver()->getAdapter()->getPathPrefix() . 'files/'. Session::get('current_pdf');
 $filename = $filepath . '/' . Session::get('current_pdf');

return response(file_get_contents($filename))->withHeaders([
 'Content-Type' => 'application/pdf'
 ]);
 }
 return redirect()->back()->with('error', 'Sorry. File not found.');
 }

public function edit()
 {
 $filepath = Storage::disk('local')->getDriver()->getAdapter()->getPathPrefix() . 'files/'. Session::get('current_pdf');
 $filename = $filepath . '/' . Session::get('current_pdf');
 $pdf = new Pdf($filename);

$fields = $pdf->getDataFields(true)->__toArray();

return view('pdftest.edit')->with('fields',$fields);
 }

public function save(Request $request)
 {
 $filepath = Storage::disk('local')->getDriver()->getAdapter()->getPathPrefix() . 'files/'. Session::get('current_pdf');
 $filename = $filepath . '/' . Session::get('current_pdf');
 $target = $filepath . '/filled_' . Session::get('current_pdf');
 $pdf = new Pdf($filename);

$inputs = $request->all();
 unset($inputs['_token']);
 foreach ($inputs as $field=>$value) {
 $data[str_replace('_',' ',$field)] = $value;
 }

$pdf->fillForm($data)
 ->needAppearances();

if (!$pdf->saveAs($target)) {
 $error = $pdf->getError();
 dd($error);
 }
 return response(file_get_contents($target))->withHeaders([
 'Content-Type' => 'application/pdf'
 ]);
 }
 }

We will also need two views, one to upload the PDF and the other to create a dynamic form that will allow us to edit the content of the PDF form.

Below is how the edit form looks like

@extends('layouts.app')

@section('content')
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-md-8">
                <div class="card">
                    <div class="card-header">PDF Test</div>

                    <div class="card-body">
                        @if (session('status'))
                            <div class="alert alert-success" role="alert">
                                {{ session('status') }}
                            </div>
                        @endif

                        @if (session()->has('success'))
                            <div class="alert alert-success">
                                <ul>
                                    <li>{!! session()->get('success') !!}</li>
                                </ul>
                            </div>
                        @endif

                        <form method="POST" action="/pdftest/save" aria-label="{{ __('Save Changes') }}">
                            @csrf

                            <table class="table table-bordered table-striped">
                                <thead>
                                    <tr>
                                        <td>Name</td>
                                        <td>Type</td>
                                        <td>Value</td>
                                    </tr>
                                </thead>
                            @foreach($fields as $field)
                                <tbody>
                                    <tr>
                                        <td>{{$field['FieldName']}}</td>
                                        <td>{{$field['FieldType']}}</td>
                                        <td>{{json_encode($field['FieldValue'])}}</td>
                                    </tr>
                                </tbody>
                            @endforeach
                            </table>
                            {{--@foreach($fields as $field)--}}
                                {{--<div class="form-group row">--}}
                                    {{--<label for="{{$field['FieldName']}}">{{ __($field['FieldName']) }}</label>--}}
                                    {{--@switch($field['FieldType'])--}}
                                        {{--@case('Text')--}}
                                            {{--<div class="col-md-6">--}}
                                                {{--<input type="text" id="{{$field['FieldName']}}" name="{{$field['FieldName']}}" value="{{json_encode($field['FieldValue'])}}">--}}
                                            {{--</div>--}}
                                        {{--@break--}}
                                        {{--@case('Choice')--}}
                                            {{--<div class="col-md-6">--}}
                                                {{--<select id="{{$field['FieldName']}}" name="{{$field['FieldName']}}" value="{{json_encode($field['FieldValue'])}}">--}}
                                                    {{--<option value="{{$field['FieldValue']}}">{{$field['FieldValue']}}</option>--}}
                                                    {{--@foreach($field["FieldStateOption"] as $option)--}}
                                                        {{--<option value="{{$option}}" >{{__($option)}}</option>--}}
                                                    {{--@endforeach--}}
                                                {{--</select>--}}
                                            {{--</div>--}}
                                        {{--@break--}}
                                        {{--@case('Button')--}}
                                            {{--<div class="col-md-6">--}}
                                                {{--@if($field['FieldValue']=="1")--}}
                                                    {{--<input type="checkbox" id="{{$field['FieldName']}}" name="{{$field['FieldName']}}" value="{{$field['FieldValue']}}" checked>--}}
                                                {{--@else--}}
                                                    {{--<input type="checkbox" id="{{$field['FieldName']}}" name="{{$field['FieldName']}}" value="{{$field['FieldValue']}}">--}}
                                                {{--@endif--}}
                                            {{--</div>--}}
                                        {{--@break--}}
                                    {{--@endswitch--}}
                                {{--</div>--}}
                            {{--@endforeach--}}

                            <div class="form-group row mb-0">
                                <div class="col-md-8 offset-md-4">
                                    <button type="submit" class="btn btn-primary">
                                        {{ __('Save changes') }}
                                    </button>
                                </div>
                            </div>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
@endsection

Once you finish just click on Save Changes button that will fill the form and save a new filled PDF.

Final thoughts

I hope this simple example illustrate on how to handle projects that requires PDF form edition.

Thank you for reading and see in the next post.

Comments

See how we can help

Lets talk!

Stay up to date on the latest technologies

Join our mailing list, we promise not to spam.