Tuesday, October 30, 2018

How to Create Simple CV Generator Using mPDF Library in PHP

In this article we will learn How to Develop a Simple CV Generator in PHP using mPDF library. We will also use CSS & JS framework bootstrap and vue.js in our project. So without any delay, let’s start

Intro:
We will develop a CV generator web app which allows user to generate a PDF CV after user enter his details i.e name, skills, experience bio and click generate button. You can checkout the scshot below:
How to Create Simple CV Generator Using mPDF Library in PHP



Folder Structure:

Our folder structure will be like this:
How to Create Simple CV Generator Using mPDF Library in PHP

The main folder (You can name it anything), In main folder we have assets folder which contain all the resources used in our project i.e CSS, JS, Images and CV template.

The vendor folder will contain our mPDF library which we will soon install it using composer, The composer.lock and composer.json are auto generated when installing the library.

Finally index.php, which will contain our main view and afcourse PHP script to generate pdf file from user input using mPDF library.

Implementation:
Save the following code in index.php

1.  <!DOCTYPE html>  
2.  <html>  
3.  <head>  
4.      <title>Simple CV Generator</title>  
5.    
6.      <!-- Latest compiled and minified CSS -->  
7.      <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">  
8.    
9.      <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>  
10.   
11.     <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>  
12.   
13.     <!--Custom CSS-->  
14.     <link rel="stylesheet" type="text/css" href="assests/css/style.css">  
15.   
16.     <!-- Latest compiled and minified JavaScript -->  
17.       
18.     <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>  
19.   
20.     <!--Font Awesome-->  
21.     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">  
22.   
23.     <!-- development version, includes helpful console warnings -->  
24.     <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
25.   
26.     <!-- Custom JS -->  
27.     <script src="assests/js/cv-form.js"></script>  
28.   
29. </head>  
30. <body>  
31.  
32. <div class="container">  
33.         <div class="row">  
34.             <div class="col-md-6 col-push-6">  
35.                 <div class="jumbotron cv-form">  
36.                     <h3 class=" text-center cv-heading">Simple CV Generator</h3>  
37.                     <p class=" text-center lead">Enter your details and get the CV in PDF format</p>  
38.                     <form method="post" action="<?php htmlspecialchars($_SERVER['PHP_SELF'])?>" class="needs-validation" novalidate id="form1">  
39.                         <div class="form-group">  
40.                             <input name="fname" id="validationCustom01" type="text" placeholder="Name" class="form-control" name="" required>  
41.                             <div class="invalid-feedback">  
42.                                 Enter Your Name  
43.                             </div>  
44.                         </div>  
45.   
46.                         <div class="form-group">  
47.                             <input name="job" type="text" id="validationCustom02" placeholder="Job Title" class="form-control" required>  
48.                             <div class="invalid-feedback">  
49.                                 Enter Job Title   
50.                             </div>  
51.                         </div>  
52.   
53.                         <div class="form-group">  
54.                             <textarea name="bio" id="bio" id="validationCustom03" placeholder="Bio" class="form-control" name="" required=""></textarea>
55.                         
56.
                        <!--Skills-->  
57.                         <div class="form-group"  v-for="(skill,index) in skills">  
58.                             <input type="text" placeholder="Skill" class="form-control" name="skills[]">  
59.                         </div>  
60.   
61.                         <div class="form-group">  
62.                             <a href="javaScript:void(0)" v-on:click="addSkill" class="text-left">Add-Skill</a>  
63.                         </div>  
64.                           
65.   
66.                         <!--Experience-->  
67.                         <div class="form-group"  v-for="(experience,index) in experiences">  
68.                             <input type="text" placeholder="Experience" class="form-control" name="exps[]">  
69.                         </div>  
70.                         <div class="form-group">  
71.                             <a href="javaScript:void(0)" v-on:click="addExp" class="text-left">Add-Experience</a>  
72.                         </div>  
73.                         <input type="submit" class="btn btn-primary" value="Generate" name="Generate">  
74.                     </form>  
75.                 </div>  
76.             </div>  
77.         </div>  
78.     </div>  
79. </body>  
80. </html>  


The head section contain external style sheets and javascript, Then In body we have created a form which contain action to itself ($_SERVER [‘PHP_SELF’] refers to the name of current file).

We also have assigned novalidate class to form main tag which will allow us to show validation message in more custom format instead of tool tips.
The div containing class “invalid-feedback” with each input will contain the error message when the input is left blank (Remember to add required attribute to input which you want to validate).

Now for adding skills we want that user should be able to add many skills as much as he want to show in his CV. So for doing that, we will have single input for each skill, When user click Add-Skill link each time then one more input appends to the skills.

We are using vue.js to perform above functionality. As you can see here we have v-for attribute assigned to skill form group. This is basically a vue.js directive which allows us to run for loop on the tag. This for loop iterate over the array, as in our case we have create a array skills in custom.js. This skills array contains the total skill inputs with their value. By default, skills array will  have a single input, So a single input will be shown for skill when user first load the page, But when user click add-skill each time a input is append to the skills variable and displayed on view.

Now as user will click a link Add-Skills, so we have to create that clickable link. So for doing that we have created a form group below the skills form group. Notice the v-on:click="addSkill" directive used in anchor tag.  v-on:click  directive will attach a event listener “addSkill” to the achor  tag. AddSkill method will be triggred and will append a new input to skills array each time it is clicked.

Also note that we have assigned an array to the name attribute in input tag of skill, this will allow us to get the skill value of each input in skills array.

Similar functionality is used for experience input.
Now in assets folder create a folder named “JS” and in that folder create a JS file named “cv-form.js” and copy paste the below code:

1.  window.onload = function () {  
2.    
3.      //Vue.Js Custom  
4.      var app=new Vue({  
5.          el:'#form1',  
6.          data:{  
7.              skills:[{value:''}],  
8.              experiences:[{value:''}]  
9.          },  
10.         methods:{  
11.             addSkill(){  
12.                 this.skills.push({value:''});  
13.             },  
14.             addExp(){  
15.                 this.experiences.push({value:''});  
16.             }  
17.         }  
18.     });  
19.   
20.   
21. // Example starter JavaScript for disabling form submissions if there are invalid fields  
22. (function() {  
23.     // Fetch all the forms we want to apply custom Bootstrap validation styles to  
24.     var forms = document.getElementsByClassName('needs-validation');  
25.     // Loop over them and prevent submission  
26.     var validation = Array.prototype.filter.call(forms, function(form) {  
27.       form.addEventListener('submit'function(event) {  
28.         if (form.checkValidity() === false) {  
29.           event.preventDefault();  
30.           event.stopPropagation();  
31.         }  
32.         form.classList.add('was-validated');  
33.       }, true);  
34.     });  
35.   
36. })();  
37.   
38. }  

The Vue JS  code is doing what I have explained above, In VUE constructor we are passing an object which contain the form id, The data object which contain all the variables and their default value , The method object which contain addSkill and addExp methods which will append new value to skills and exp variables respectively as we have discussed it above.

In addition to that, we also have another function which is used for bootstrap validation. Just copy and paste it. This function disables form submissions if there are invalid fields

For some custom styling create a folder named “CSS” in assets folder and in that folder create a file named “style.css”  and copy paste the following styles:
1.  body{  
2.      background:url('../images/bg.png') no-repeat;  
3.      background-size:100% auto;  
4.      font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";  
5.    
6.  }  
7.    
8.  .cv-form{  
9.    
10.     margin-top:100px;  
11. }  
12.   
13.   
14. .lead{  
15.     font-size: 98%;  
16. }  
17.   
18. .custom-select.is-valid, .form-control.is-valid, .was-validated .custom-select:valid, .was-validated .form-control:valid{  
19.     border-color: #ced4da;  
20. }  

We are finished adding client side functionality, Now let’s move ahead to add code that will be executed on server!

But wait, Before writing PHP code we have to install a library named “mPDF” using composer.This library will do all the magic i.e generating pdf file from html.

So as PHP developer, you should know what is composer. But if you don’t know about it then, let me give you a brief definition. 

What is Composer:
“A composer is a dependency manager for PHP which will pull all the libraries and their dependency required for your project”.

You can download composer from its official website.

Installing mPDF Library:After downloading and installing composer, let’s move ahead to install mPDF library required for our project using composer

To install mdpf, Open cmd then navigate to the directory which contain your project and use the below composer command:

composer require mpdf/mpdf
This command will install the mPDF lirary inside the vendor folder and will also generate the composer.lock and composer.json files in your project main folder.
(Note: vendor directory will be automatically created by composer; you don’t have to create it)

After installing library, we are ready to go to do the magic!

Add the following PHP code at the bottom of the main index.php file.


  1. <?php  
  2.     if(isset($_POST['Generate'])){  
  3.         require_once __DIR__ . '/vendor/autoload.php';  
  4.         ob_start();  
  5.         include("assests/templete/html.php");  
  6.         $html=ob_get_clean();  
  7.         $style = file_get_contents("assests/templete/style.css");  
  8.         $mpdf = new \Mpdf\Mpdf();  
  9.         $mpdf->WriteHTML($style,1);  
  10.         $mpdf->WriteHTML($html,2);  
  11.         ob_clean();   
  12.         $mpdf->Output();  
  13.     }  
  14. ?>  

Now, as this library allow us to generate PDF file from html, we have to write the html and styles for our CV. You can do this by your own or you can download html CV templates from internet too or can simply copy the below codes.

To do this, create a folder named “template” in assets folder and create html.php and style.css files in that folder with below codes respectively:


html.php
1.  <!DOCTYPE html>  
2.  <html>  
3.  <head>  
4.  <meta name="viewport" content="width=device-width"/>  
5.  <meta name="description" content="The Curriculum Vitae of Joe Bloggs."/>  
6.  <meta charset="UTF-8">   
7.    
8.  <link type="text/css" rel="stylesheet" href="style.css">  
9.  <link href='http://fonts.googleapis.com/css?family=Rokkitt:400,700|Lato:400,300' rel='stylesheet' type='text/css'>  
10.   
11. <!--[if lt IE 9]>  
12. <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>  
13. <![endif]-->  
14. </head>  
15. <body id="top">  
16. <div id="cv" class="instaFade">  
17.     <div class="mainDetails">  
18.         <div id="headshot" class="quickFade">  
19.             <img src="headshot.jpg" alt="Alan Smith" />  
20.         </div>  
21.           
22.         <div id="name">  
23.             <h1 class="quickFade delayTwo"><?php echo $_POST['fname']; ?></h1>  
24.             <h2 class="quickFade delayThree"><?php echo $_POST['job']; ?></h2>  
25.         </div>  
26.         <div class="clear"></div>  
27.     </div>  
28.       
29.     <div id="mainArea" class="quickFade delayFive">  
30.         <section>  
31.             <article>  
32.                 <div class="sectionTitle">  
33.                     <h1>Personal Profile</h1>  
34.                 </div>  
35.                   
36.                 <div class="sectionContent">  
37.                     <p><?php echo $_POST['bio']; ?></p>  
38.                 </div>  
39.             </article>  
40.             <div class="clear"></div>  
41.         </section>  
42.           
43.           
44.         <section>  
45.             <div class="sectionTitle">  
46.                 <h1>Work Experience</h1>  
47.             </div>  
48.               
49.             <div class="sectionContent">  
50.                 <?php foreach($_POST['exps'as $exp){?>  
51.                     <article>  
52.                         <h3><?php echo $exp; ?></h3>  
53.                     </article>  
54.                 <?php } ?>              
55.             </div>  
56.             <div class="clear"></div>  
57.         </section>  
58.         <section>  
59.             <div class="sectionTitle">  
60.                 <h1>Key Skills</h1>  
61.             </div>  
62.               
63.             <div class="sectionContent">  
64.                 <ul class="keySkills">  
65.                     <?php foreach($_POST['skills'as $skill){?>  
66.                         <li><?php echo $skill; ?></li>  
67.                     <?php } ?>  
68.                 </ul>  
69.             </div>  
70.             <div class="clear"></div>  
71.         </section>  
72.     </div>  
73. </div>  
74.   
75. </body>  
76. </html>  

style.css
1.  html,body,div,span,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,abbr,address,cite,code,del,dfn,em,img,ins,kbd,q,samp,small,strong,sub,sup,var,b,i,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,figcaption,figure,footer,header,hgroup,menu,nav,section,summary,time,mark,audio,video {  
2.  border:0;  
3.  font:inherit;  
4.  font-size:100%;  
5.  margin:0;  
6.  padding:0;  
7.  vertical-align:baseline;  
8.  }  
9.    
10. article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section {  
11. display:block;  
12. }  
13.   
14. html, body { font-family'Lato'helveticaarialsans-seriffont-size16pxcolor#222;}  
15.   
16. .clear {clearboth;}  
17.   
18. p {  
19.     font-size1em;  
20.     line-height1.4em;  
21.     margin-bottom20px;  
22.     color#444;  
23. }  
24.   
25. #cv {  
26.     width90%;  
27.     max-width800px;  
28.     background#f3f3f3;  
29.     margin30px auto;  
30. }  
31.   
32. .mainDetails {  
33.     padding25px 35px;  
34.     border-bottom2px solid #cf8a05;  
35.     background#ededed;  
36. }  
37.   
38. #name h1 {  
39.     font-size2.5em;  
40.     font-weight700;  
41.     font-family'Rokkitt'HelveticaArialsans-serif;  
42.     margin-bottom-6px;  
43. }  
44.   
45. #name h2 {  
46.     font-size2em;  
47.     margin-left2px;  
48.     font-family'Rokkitt'HelveticaArialsans-serif;  
49. }  
50.   
51. #mainArea {  
52.     padding0 40px;  
53. }  
54.   
55. #headshot {  
56.     width12.5%;  
57.     floatleft;  
58.     margin-right30px;  
59. }  
60.   
61. #headshot img {  
62.     width100%;  
63.     heightauto;  
64.     -webkit-border-radius: 50px;  
65.     border-radius: 50px;  
66. }  
67.   
68. #name {  
69.     floatleft;  
70. }  
71.   
72. #contactDetails {  
73.     floatright;  
74. }  
75.   
76. #contactDetails ul {  
77.     list-style-typenone;  
78.     font-size0.9em;  
79.     margin-top2px;  
80. }  
81.   
82. #contactDetails ul li {  
83.     margin-bottom3px;  
84.     color#444;  
85. }  
86.   
87. #contactDetails ul li a, a[href^=tel] {  
88.     color#444;   
89.     text-decorationnone;  
90.     -webkit-transition: all .3s ease-in;  
91.     -moz-transition: all .3s ease-in;  
92.     -o-transition: all .3s ease-in;  
93.     -ms-transition: all .3s ease-in;  
94.     transition: all .3s ease-in;  
95. }  
96.   
97. #contactDetails ul li a:hover {   
98.     color#cf8a05;  
99. }  
100.           
101.           
102.         section {  
103.             border-top1px solid #dedede;  
104.             padding20px 0 0;  
105.         }  
106.           
107.         section:first-child {  
108.             border-top0;  
109.         }  
110.           
111.         section:last-child {  
112.             padding20px 0 10px;  
113.         }  
114.           
115.         .sectionTitle {  
116.             floatleft;  
117.             width25%;  
118.         }  
119.           
120.         .sectionContent {  
121.             floatright;  
122.             width72.5%;  
123.         }  
124.           
125.         .sectionTitle h1 {  
126.             font-family'Rokkitt'HelveticaArialsans-serif;  
127.             font-styleitalic;  
128.             font-size1.5em;  
129.             color#cf8a05;  
130.         }  
131.           
132.         .sectionContent h2 {  
133.             font-family'Rokkitt'HelveticaArialsans-serif;  
134.             font-size1.5em;  
135.             margin-bottom-2px;  
136.         }  
137.           
138.         .subDetails {  
139.             font-size0.8em;  
140.             font-styleitalic;  
141.             margin-bottom3px;  
142.         }  
143.           
144.         .keySkills {  
145.             list-style-typenone;  
146.             -moz-column-count:3;  
147.             -webkit-column-count:3;  
148.             column-count:3;  
149.             margin-bottom20px;  
150.             font-size1em;  
151.             color#444;  
152.         }  
153.           
154.         .keySkills ul li {  
155.             margin-bottom3px;  
156.         }  
157.           
158.         @media all and (min-width602px) and (max-width800px) {  
159.             #headshot {  
160.                 displaynone;  
161.             }  
162.               
163.             .keySkills {  
164.             -moz-column-count:2;  
165.             -webkit-column-count:2;  
166.             column-count:2;  
167.             }  
168.         }  
169.           
170.         @media all and (max-width601px) {  
171.             #cv {  
172.                 width95%;  
173.                 margin10px auto;  
174.                 min-width280px;  
175.             }  
176.               
177.             #headshot {  
178.                 displaynone;  
179.             }  
180.               
181.             #name#contactDetails {  
182.                 floatnone;  
183.                 width100%;  
184.                 text-aligncenter;  
185.             }  
186.               
187.             .sectionTitle, .sectionContent {  
188.                 floatnone;  
189.                 width100%;  
190.             }  
191.               
192.             .sectionTitle {  
193.                 margin-left-2px;  
194.                 font-size1.25em;  
195.             }  
196.               
197.             .keySkills {  
198.                 -moz-column-count:2;  
199.                 -webkit-column-count:2;  
200.                 column-count:2;  
201.             }  
202.         }  
203.           
204.           
205.         .quickFade {  
206.             -webkit-animation-name: reset, fade-in;  
207.             -webkit-animation-duration: 2.5s;  
208.             -webkit-animation-timing-function: ease-in;  
209.               
210.             -moz-animation-name: reset, fade-in;  
211.             -moz-animation-duration: 2.5s;  
212.             -moz-animation-timing-function: ease-in;  
213.               
214.             animation-name: reset, fade-in;  
215.             animation-duration: 2.5s;  
216.             animation-timing-function: ease-in;  
217.         }  
218.            
219.         .delayOne {  
220.             -webkit-animation-delay: 0, .5s;  
221.             -moz-animation-delay: 0, .5s;  
222.             animation-delay: 0, .5s;  
223.         }  
224.           
225.         .delayTwo {  
226.             -webkit-animation-delay: 01s;  
227.             -moz-animation-delay: 01s;  
228.             animation-delay: 01s;  
229.         }  
230.           
231.         .delayThree {  
232.             -webkit-animation-delay: 01.5s;  
233.             -moz-animation-delay: 01.5s;  
234.             animation-delay: 01.5s;  
235.         }  
236.           
237.         .delayFour {  
238.             -webkit-animation-delay: 02s;  
239.             -moz-animation-delay: 02s;  
240.             animation-delay: 02s;  
241.         }  
242.           
243.         .delayFive {  
244.             -webkit-animation-delay: 02.5s;  
245.             -moz-animation-delay: 02.5s;  
246.             animation-delay: 02.5s;  
247.         }  



You can notice that in html.php file, we are capturing the user inputs to display them in CV using the PHP Post array.

Also Read: How Access and Handle Form Data in PHP (POST And GET) 

So in PHP, Firstly we are checking to make sure that submit button is clicked before the code executes by using isset function.

Now we have to get the html and styles from the files we created above, because mPDF library will use them to generate a PDF file.

Since we are using PHP variables in html.php, we cannot use file_get_contents method to get all the html, because this method will convert all text even the PHP code in string. So to overcome this issue, we can use the concept of output buffering.

We will get the html by simply including the html.php file using PHP include function. But instead of returning the content from html.php, We are storing the contents of html.php in buffer memory. 

To tell PHP about which content to buffer we have to use ob_start() before the buffered content. Now we can simply return the buffered content in $html variable using ob_get_clean. This method will return the buffer content and clean the buffer memory. 

Since style.css is simply a text, we can get it using file_get_contents().

Now we have to pass the html and style variables to the mpDF library writeHtml method. After passing the content, we are simply calling the method “$mpdf->output”, It will send the content in downloadable PDF format.

Here is example of what PDF file will contain:
How to Create Simple CV Generator Using mPDF Library in PHP
How to Create Simple CV Generator Using mPDF Library in PHP


Want to add more functionality? Download complete source code and add your customization!
Sharing is Caring, Share this Article to Your Coding Community, Thanks.

No comments:

Post a Comment