Document Template

How to create invoices easily with document-templates package

Daniel Verner -

Introduction
There are some common features almost every web application has: registration, login, email sending, pdf generation, admin interface, etc. We created our document-templates Laravel package to allow the admins to easily edit their email (and any other) templates using a WYSIWYG editor. This package is a generic templating system, which can be used to create any output format like HTML, markdown, txt, etc, but the most common usage is to use HTML template. The package comes with build-in pdf rendering engine and it can be used seamlessly with Laravel mailables. The package uses Twig as a template engine.

Why Twig instead of Blade?
This is a logical question, why would we use different template engines? We already have a good build-in template engine in Laravel. The basic idea was to allow the users/admins to put some logic into the templates like ifs, for loops, etc. It could be easily solved also with Blade as well, but for security reasons, I wanted to allow the developers to have control over what functions/logic can be used in the editable part of the template. Twig has a built-in configurable sandboxing feature. The sandbox is applied to every part of the templates which can be edited by the users/admins.

How it works?
We create a basic layout of the document and define which parts of it can be editable by the user. To achieve this we use the block feature of Twig. In the next step, we create a document class that uses a trait from the package and defines the data sources for the document. Every document class represents a document type in your application. For example, you should create different classes for the invoice, confirmation email, a registration email, etc. Render the document on the proper place in your application (for example generate the invoice when the payment finished).

How can I use it?
The architecture of the package consists of 3 main parts:

  1. The core features include the twig engine, data sources, and the layout
  2. The rendering module allows the document rendering as HTML, pdf, etc
  3. The admin part comes with routes, controller, vue component, and Ckeditor to quickly build the administration for the documents
You can decide if you want to simply use the core features and use the advantages of twig, and build the other parts yourself. You can also utilize the pdf rendering capabilities, or use the whole package with all the belts and whistles.

The conceptual diagram of the package can be found below:

Let’s build something!
In the next paragraphs, we will build an invoice generation system from scratch using this package.

Installation
Install the package with composer:
composer require 42coders/document-templates
Publish the migrations, views, vue components and config:
php artisan vendor:publish --provider="BWF\DocumentTemplates\DocumentTemplatesServiceProvider"
Run the migration
php artisan migrate
 
Create the base layout
Create the template file: resources/templates/InvoiceTemplate.html.twig with the following content:
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Invoice - #123</title>
 
    <style type="text/css">
        @page {
            margin: 0px;
        }
 
        body {
            margin: 0px;
        }
 
        * {
            font-family: Verdana, Arial, sans-serif;
        }
 
        a {
            color: #fff;
            text-decoration: none;
        }
 
        table {
            font-size: x-small;
        }
 
        tfoot tr td {
            font-weight: bold;
            font-size: x-small;
        }
 
        .invoice table {
            margin: 15px;
        }
 
        .invoice h3 {
            margin-left: 15px;
        }
 
        .information {
            background-color: rgb(85, 47, 218);
            color: #FFF;
        }
 
        .information .logo {
            margin: 5px;
        }
 
        .information table {
            padding: 10px;
        }
    </style>
 
</head>
<body>
 
<div class="information">
    <table width="100%">
        <tr>
            <td align="left" style="width: 40%;">
                {% block customer_details %}
                {% endblock %}
<br/><br/>
<pre>
Date: 2018-01-01
Identifier: #uniquehash
Status: Paid
</pre>
 
 
            </td>
            <td align="center">
                <img src="https://42coders.com/wp-content/uploads/elementor/thumbs/logo-o9jp5a9c6jnn02f7yrqrfuzv6pr1lrqw5amfzuffv4.png" alt="Logo" width="64" class="logo"/>
            </td>
            <td align="right" style="width: 40%;">
 
                <h3>42coders</h3>
                <pre>
                    https://42coders.com
 
                    Schopenhauerstr. 71
                    80807 München
                </pre>
            </td>
        </tr>
 
    </table>
</div>
 
 
<br/>
 
<div class="invoice">
    <h3>Invoice specification #123</h3>
        {% block order_items %}
        {% endblock %}
</div>
 
<div class="information" style="position: absolute; bottom: 0;">
    <table width="100%">
        <tr>
            <td align="left" style="width: 50%;">
            </td>
            <td align="right" style="width: 50%;">
                42coders
            </td>
        </tr>
 
    </table>
</div>
</body>
</html>
In the above template we’ve defined 2 blocks: customer_details and order_items. These blocks will be editable in the admin area with Ckeditor.

Create the document class
Let’s create our document class in app/DocumentTemplates/InvoiceTemplate.php:
<?php
 
 
namespace App\DocumentTemplates;
 
use App\User;
use BWF\DocumentTemplates\DocumentTemplates\DocumentTemplate;
use BWF\DocumentTemplates\DocumentTemplates\DocumentTemplateInterface;
 
class InvoiceTemplate implements DocumentTemplateInterface
{
    use DocumentTemplate;
 
    protected $testOrders = [
        [
            'id' => '1',
            'description' => 'Package Basic',
            'price' => 10
        ],
        [
            'id' => '2',
            'description' => 'Package Pro',
            'price' => 20
        ],
        [
            'id' => '3',
            'description' => 'Package Advanced',
            'price' => 30
        ],
    ];
 
    protected function dataSources()
    {
        return [
            $this->dataSource($this->testOrders[0], 'order', true, 'orders'),
            $this->dataSource(new User(), 'user'),
        ];
    }
 
}
In this step we create the document class, and define the datasources for the document. For the sake of simplicity we use an array as the order data, and the User model for the customer data. To be able to use the User as a datasource for the invoice, it needs to implement  the TemplateDataSourceInterface, so we will use the ModelProvidesTemplateData trait to achieve this:
<?php
 
namespace App;
 
use BWF\DocumentTemplates\TemplateDataSources\ModelProvidesTemplateData;
use BWF\DocumentTemplates\TemplateDataSources\TemplateDataSourceInterface;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
 
class User extends Authenticatable implements TemplateDataSourceInterface
{
    use Notifiable, ModelProvidesTemplateData;

Create controller
We create a controller for administration and to test the document editing/rendering:
<?php
 
namespace App\Http\Controllers;
 
use App\DocumentTemplates\InvoiceTemplate;
use BWF\DocumentTemplates\DocumentTemplates\DocumentTemplateFactory;
use BWF\DocumentTemplates\DocumentTemplates\DocumentTemplateModelInterface;
use BWF\DocumentTemplates\Http\Controllers\DocumentTemplatesController;
use BWF\DocumentTemplates\TemplateDataSources\TemplateDataSource;
use BWF\DocumentTemplates\TemplateDataSources\TemplateDataSourceFactory;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
 
class InvoiceTemplatesController extends DocumentTemplatesController
{
    protected $testOrders = [
        [
            'id' => '1',
            'description' => 'Package Basic',
            'price' => 10
        ],
        [
            'id' => '2',
            'description' => 'Package Pro',
            'price' => 20
        ],
        [
            'id' => '3',
            'description' => 'Package Advanced',
            'price' => 30
        ],
    ];
 
    /**
     * @return TemplateDataSource[]
     */
    protected function getTestOrders()
    {
        $dataSources = [];
 
        foreach ($this->testOrders as $item) {
            $dataSources[] = TemplateDataSourceFactory::build($item, 'order');
        }
 
        return collect($dataSources);
    }
 
    protected $documentClasses = [
        InvoiceTemplate::class,
    ];
 
    protected function getTemplateData()
    {
        return [
            'user' => Auth::user(),
            'orders' => $this->getTestOrders(),
        ];
    }
 
    public function show(DocumentTemplateModelInterface $documentTemplateModel)
    {
 
        $documentTemplate = DocumentTemplateFactory::build($documentTemplateModel);
 
        $templateData = $this->getTemplateData();
 
        foreach ($templateData as $name => $data) {
            $documentTemplate->addTemplateData($data, $name);
        }
 
        return $documentTemplate->render();
 
    }
}
Add the package routes to the routes file:
\BWF\DocumentTemplates\DocumentTemplates::routes(InvoiceTemplatesController::class);

Create seeders
Now it is time to fill the database with some test data. Create seeder for editable templates database/seeds/EditableTemplatesTableSeeder.php:
<?php
 
use Illuminate\Database\Seeder;
 
class EditableTemplatesTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $documentTemplate = \BWF\DocumentTemplates\DocumentTemplates\DocumentTemplateModel::create([
            'name' => 'Invoice',
            'document_class' => \App\DocumentTemplates\InvoiceTemplate::class,
            'layout' => 'InvoiceTemplate.html.twig'
        ]);
 
        $documentTemplate->save();
 
 
        \BWF\DocumentTemplates\EditableTemplates\EditableTemplate::create([
            'document_template_id' => $documentTemplate->id,
            'name' => 'order_items',
            'content' => '
    <table width="100%">
        <thead>
        <tr>
            <th>Description</th>
            <th>Quantity</th>
            <th>Total</th>
        </tr>
        </thead>
        <tbody>
                {% for order in orders %}
                    <tr>
                        <td>
                            {{order.description}}
                        </td>
                        <td>1</td>
                        <td>
                            €{{order.price}}
                        </td>
                    </tr>
                {% endfor %}
                            </tbody>
 
        <tfoot>
        <tr>
            <td colspan="1"></td>
            <td align="left">Total</td>
            <td align="left" class="gray">€60,-</td>
        </tr>
        </tfoot>
    </table>
                '
        ])->save();
 
        \BWF\DocumentTemplates\EditableTemplates\EditableTemplate::create([
            'document_template_id' => $documentTemplate->id,
            'name' => 'customer_details',
            'content' => '
                <h3>{{user.name}}</h3>
                <pre>
Street 15
123456 City
United Kingdom
</pre>
                '
        ])->save();
    }
}
database/seeds/UsersTableSeeder.php:
<?php
 
use Illuminate\Database\Seeder;
 
class UsersTableSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        factory(App\User::class, 1)->create(
            [
                'name' => 'Administrator',
                'email' => 'admin@bwf',
                'password' => bcrypt('secret')
            ]
        );
    }
}
Add these seeders to the DatabaseSeeder:
    public function run()
    {
        $this->call(UsersTableSeeder::class);
        $this->call(EditableTemplatesTableSeeder::class);
    }
Run the seeders:
php artisan db:seed
If you use Laravel 6 you might need to create frontend scaffolding, please check the Laravel documentation here:
composer require laravel/ui --dev
php artisan ui bootstrap
php artisan ui vue
Compile the assets:
npm run dev

Create the app layout – optional
If your application don’t have already, create the app layout: resources/views/layouts/app.blade.php:
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
 
    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">
 
    <title>{{ config('app.name', 'Laravel') }}</title>
 
    <!-- Scripts -->
    <script src="{{ asset('js/app.js') }}" defer></script>
 
    <!-- Fonts -->
    <link rel="dns-prefetch" href="//fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet" type="text/css">
 
    <!-- Styles -->
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
    <div id="app">
        <nav class="navbar navbar-expand-md navbar-light navbar-laravel">
            <div class="container">
                <a class="navbar-brand" href="{{ url('/') }}">
                    {{ config('app.name', 'Laravel') }}
                </a>
                <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
                    <span class="navbar-toggler-icon"></span>
                </button>
 
                <div class="collapse navbar-collapse" id="navbarSupportedContent">
                    <!-- Left Side Of Navbar -->
                    <ul class="navbar-nav mr-auto">
                        <li class="nav-item">
                            <a class="nav-link" href="{{ route(config('document_templates.base_url') . '.index') }}">{{ __('Document templates') }}</a>
                        </li>
                    </ul>
 
                    <!-- Right Side Of Navbar -->
                    <ul class="navbar-nav ml-auto">
                    </ul>
                </div>
            </div>
        </nav>
 
        <main class="py-4">
            @yield('content')
        </main>
    </div>
</body>
</html>

Let’s try it
If everything went well when we navigate to the /document-templates/ URL, we should see something like this:

The edit form should look like this:

and the generated invoice with with the placeholders filled should look like this:

Closing thoughts
The package has been tested with Laravel version >=5.7. For more information on how to use the package and for more code examples, please check the source code of the demo application here and the package documentation.

Tags: Laravel · open-source · document-templates · Documents · Email-Templates · Invoices

Want products news and updates?

Sign up for our newsletter to stay up to date.

We care about the protection of your data. Read our Privacy Policy.

Impressions from our Team

  • Happy birthday 🎁🎈🎂 Filip - #

  • Another day another #mandarinacakeshop 🎂 😀 - #

  • Happy Birthday Ognjen! And marry Christmas to all other 🎄#notacakeshop - #

  • #Office #Garden - #

  • #workhard - #

  • #belgrade #skyline - #

  • #happybirthday Phil :) - #

  • #happybirthday Stefan 🥂 - #

  • #happybirthday Lidija 🍾 - #

  • Say hi 👋 to our newest team member ☕️ - #

  • #bithday #cake 😻 - #

  • #stayathome #homeoffice #42coders - #

  • #stayathome #homeoffice #42coders #starwars :) - #

  • #stayathome #homeoffice #42coders - #

  • We had a really nice time with #laracononline #laravel - #

  • Happy Birthday 🎂 Miloš - #

  • Happy Birthday 🎂Nikola - #

  • #42coders #christmas #dinner what a nice evening :) - #

  • Happy Birthday 🎂 Ognjen - #

  • Wish you all a merry Christmas 🎄🎁 - #

See more!

© 2024 42coders All rights reserved.