Anzeige
Smartes Hosting für anspruchsvolle Projekte.
↬ Loslegen und vServer testen ↬ Jetzt testen!
Thomas Weise 8. Mai 2009

PDF-Rechnung mit PHP erzeugen

Wir setzen den Artikel PDF-Dokumente mit PHP erzeugen fort und erstellen die Grundlage einer Rechnung im PDF-Format direkt auf dem Server. Zur Anwendung kommen dabei zwei PHP-Klassen.

Hier geht es um die technische Realisierung der PDF-Erzeugung. Der Rechnungsinhalt ist erst einmal unwichtig und kann später angepasst werden. Auf dem Webserver muss PHP5 verfügbar sein. Falls PHP5 nicht verfügbar ist, lesen Sie bitte den oben genannten Beitrag. Dort wird unter anderem gezeigt, wie man PHP5 umgehen kann.

Folgende Verzeichnisstruktur sollten Sie vorliegen haben:

  • Legen Sie nun eine Datei rechnung.php im Projektverzeichnis an und kopieren den Inhalt folgender Datei dort hinein. rechnung.php kopieren
  • Legen Sie danach eine Datei TwPdfRechnung.php im Verzeichnis twpdf an und kopieren den Inhalt folgender Datei dort hinein. TwPdfRechnung.php kopieren
  • Öffnen Sie die soeben erstellte Datei rechnung.php in Ihrem Browser. Dieser sollte dann schon die generierte PDF-Datei anzeigen (wie in dieser Demo).

Beschreibung der Datei ‚rechnung.php‘

Zuerst wird die FPDF-Klasse und die Klasse TwPdfRechnung eingebunden.

// FPDF-Zeugs und die spezielle TwPdf-Klasse includen
define("FPDF_FONTPATH","fpdf16/font/");
include_once('fpdf16/fpdf.php');
include_once("twpdf/TwPdfRechnung.php");

Danach schreiben wir alles in Variablen, was später auf der Rechnung auftauchen soll. Dies könnten Sie natürlich auch alles in einem Formular abfragen, hier wird dies jedoch fest in den Code geschrieben, um die Komplexität dieses Artikels nicht unnötigerweise aufzublähen.

Wichtig ist dann noch, diese Variablen in eine PHP-Session zu schreiben

// alles in die Session rein
$_SESSION['twArrRechnungsdaten']      = $arrDat;
$_SESSION['twArrRechnungspositionen'] = $arrPos;

und die Klasse TwPdfRechnung aufzurufen.

// pdf erzeugen
$twpdf = new TwPdfRechnung(); 

// pdf ausgeben (im Browser oder in Datei schreiben)
$twpdf->Output();   // Ausgabe (wenn in Datei schreiben, dateiname in Klammer)

Wenn man bei Output() einen Dateiname mit angibt (beispielsweise: Output(„meinedatei.pdf“);), dann wird das erzeugte PDF in eine Datei mit dem angegebenen Name geschrieben. Wir möchten jedoch die PDF-Datei nur im Browser anzeigen und schreiben deshalb Output();.

Wichtig in diesem Zusammenhang ist noch, dass in dieser Datei VOR dem öffnenden <?php und NACH dem schließenden ?> kein einziges Zeichen mehr steht, auch kein Leerzeichen, Zeilenumbruch oder Sonstiges.

Beschreibung der Datei ‚TwPdfRechnung.php‘

Der Code dieser Datei sieht auf dem ersten Blick für Sie eventuell erst einmal sehr kompliziert aus. Aber das ist wie mit jeder neuen Sache, hat man es erstmal begriffen, dann ist es auf einmal ganz einfach.

Also lassen Sie sich bitte nicht von dem vielen Code abschrecken, ein Großteil davon ist jetzt erstmal gar nicht so wichtig. Ganz grob gesagt, sind für Sie zu Beginn diese 4 Funktionen interessant:

__construct()
wird beim Erzeugen eines Objektes dieser Klasse automatisch aufgerufen, lädt die PHP-Session-Variablen, erledigt alle für die PDF-Erzeugung relevanten Dinge
Header()
erstellt die Kopfzeile, wird automatisch aufgerufen, in unserem Falle wird hier auch gleich das Layout der gesamten Seite festgelegt
Footer()
erstellt die Fußzeile, wird automatisch aufgerufen
twShowRechnungspositionen()
stellt die einzelnen Positionen der Rechnung in einer Art Tabelle dar, muss extra aufgerufen werden

Nun diese Datei nochmal im Einzelnen. Am Anfang werden wieder die für diese Klasse relevanten Variablen (-Arrays) deklariert.

  // Variablen und Arrays
  private $twArrRechnungsdaten      = array();
  private $twArrRechnungspositionen = array();
  private $twArrSpaltenbreiten      = array();
  private $twArrSpaltenkoepfe       = array();

die Funktion __construct()

Das Seitenformat, die Maßeinheit und die Seitengröße festlegen.

    // Konstruktor der vererbenden Klasse (FPDF) aufrufen
    parent::__construct('P', 'mm', 'A4'); // L=Querformat(Landscape), P=Hochformat(Portrait)

Unsere in der Datei rechnung.php festgelegten Inhalte der Rechnung aus der PHP-Session laden.

    // Session-Variablen aus dem aufrufenden Skript übernehmen
    $this->twArrRechnungsdaten      = $_SESSION['twArrRechnungsdaten'];
    $this->twArrRechnungspositionen = $_SESSION['twArrRechnungspositionen'];

Noch ein paar grundlegende Anweisungen, wie groß die Seite im Browser angezeigt werden soll, wann ein Seitenumbruch erfolgen soll und (nicht so wichtig) die Gesamtanzahl der Seiten ermitteln.

    // Einstellungen für das PDF
    $this->SetDisplayMode( 100 );         // wie groß wird Seite angezeigt(in %)
    $this->SetAutoPageBreak(true, 50);    // 50mm von unten erfolgt ein Seitenumbruch
    $this->AliasNbPages();                // Anzahl der Seiten berechnen ({nb}-sache)

Die eigentliche Erzeugung des PDF

    // Seite erzeugen
    $this->AddPage();                     // PDF starten (ruft auch Header() und Footer() auf

Und noch zwei Dinge, speziell für unsere PDF-Rechnung. Aufruf unserer Funktion zur Erstellung der Tabelle mit den Rechnungspositionen und einer speziellen Funktion, die unsere Zahlungsbedingungen und den Gesamtbetrag der Rechnung bei mehrseitigen Rechnungen nur auf der letzten Seite anzeigt.

    // zusätzliche Sachen
    $this->twShowRechnungspositionen();   // Tabelle mit allen Rechnungspositionen
    $this->twShowLetzteSeite();           // nur auf der letzten Seite

die Funktion Header()

Hier geht es nun endlich an das eigentliche „Designen“ der Rechnung. Um die einzelnen Anweisungen besser zu verstehen, können sie die Beschreibungen in der offiziellen FPDF-Dokumentation nachlesen. Dort wird in kurzen, verständlichen Sätzen erklärt, worum es bei jeder Anweisung geht. Die Doku kann von dort auch (natürlich als PDF-Datei) heruntergeladen werden.

Die Funktion Header() ist grob in drei Bereiche unterteilt (gilt für mehrseitige Rechnungen):

  • Bereiche, die auf jeder Seite angezeigt werden
  • Bereiche, die nur auf der ersten Seite angezeigt werden
  • Bereiche, die auf allen Seiten, außer auf der Ersten angezeigt werden

Meist kommen in dieser Funktion immer wieder dieselben Anweisungen vor. Das ist das Wesen von FPDF, von oben herunter jedes einzelne Element immer wieder neu erstellen. Der Quellcode in dieser Funktion ist einigermaßen kommentiert, sodass sich vielleicht einige Dinge von selbst erklären. Die wichtigsten Anweisungen sind:

  • SetFont legt die Schriftart fest
  • SetTextColor legt die Textfarbe fest
  • SetFillColor legt die Füllfarbe fest (Hintergrund)
  • SetDrawColor legt die Rahmenfarbe fest
  • SetLineWidth legt die Rahmenstärke fest
  • twRundeckbereich legt die Rundung der Boxen fest (eigene Funktion)
  • SetXY legt Position fest (meist die Startposition einer folgenden Anweisung)
  • Cell zeichnet eine Art Zelle, meist mit enthaltenem Text (mit oder ohne Rahmen und Hintergrund)
  • Image fügt ein Bild ein

Als Bild bietet sich ein Logo an. Speichern Sie das Bild im Verzeichnis ‚twPdf‘ oder passen Sie im Quellcode den Pfad zu Ihrem Logo an.

die Funktion Footer()

Für diese Funktion gilt PDF-mäßig dasselbe wie für die vorherige Header(), nur dass hier halt die Fußzeile erstellt wird.

die Funktion twShowRechnungspositionen()

Das ist die etwas komplizierte Funktion. Sie ist nicht direkt bei FPDF enthalten, sondern selbst programmiert. Hier wird eine Art Tabelle für die einzelnen Rechnungspositionen erzeugt.

Wir geben zuerst die Spaltenbreiten (in mm) und die Beschriftungen der Spaltenköpfe an.

    // Spaltenbreiten und Beschriftung der Spaltenköpfe festlegen
    $this->twSetSpaltenbreiten(array(8, 99, 14, 20, 20));
    $this->twSetSpaltenkoepfe(array('Pos', 'Text', 'Menge', 'Preis', 'Gesamt'));

Nach ein paar Formatanweisungen erzeugt folgender Code die Spaltenköpfe.

    for ($i=0; $i<count($this->twArrSpaltenkoepfe); $i++) {
      $this->Cell($this->twArrSpaltenbreiten[$i], 7, $this->twArrSpaltenkoepfe[$i], 1, 0, 'C', 1);
    }
    $this->ln();

Die einzelnen Zeilen für die Rechnungspositionen werden folgendermaßen erstellt. Die darin für jede Zeile aufgerufene Funktion twShowZeileMitMultiCell ist wieder eine selbst programmierte Funktion.

    foreach ($this->twArrRechnungspositionen as $pos) {
      $i++;
      $this->twShowZeileMitMultiCell(array(
              $i,
              $pos['text'],
              sprintf("%9.2f", $pos['menge']),
              sprintf("%9.2f", $pos['einzelpreis']),
              sprintf("%9.2f", $pos['gesamtpreis'])
              ));
      $this->SetX(27);  // sonst gehts immer ganz links los...
    }
    $this->Cell(array_sum($this->twArrSpaltenbreiten), 0, '', 'T');  //Tabellenlinie unten

sonstige (eigene) Funktionen

In dieser Klasse sind noch weitere Funktionen programmiert, auf die hier nicht näher eingegangen werden soll, um den Umfang dieses Artikels nicht zu sprengen.

Es handelt sich dabei um die Funktion twShowLetzteSeite(), sowie um den Quellcode zwischen

  /* twTabelleMitMultiCell START -------------------------------------------- */
  ...
  ...
  /* twTabelleMitMultiCell END ---------------------------------------------- */

und

  /* twRundeckbereich START ------------------------------------------------- */
  ...
  ...
  /* twRundeckbereich END --------------------------------------------------- */

Schlussbemerkungen

Dieses Rechnungs-Beispiel ist ausbaufähig, stellt aber mit der Klasse TwPdfRechnung eine solide Vorlage dar. Anhand dieser Klasse kann man nach einer gewissen Einarbeitungszeit seine an den eigenen Geschmack angepasste PDF-Rechnung zurechtzimmern. Der Quellcode dieses Beispiels darf frei verwendet werden (siehe twPdf-Rechnung).

Die Anregungen und das Grundverständnis vor allem für die Tabelle mit MultiCell und den abgerundeten Ecken wurden von Add-On’s von FPDF bezogen. Für alle, denen es nicht schnell genug gehen kann, gibt es den Quellcode in einem ZIP-Archiv. ™

Thomas Weise

Thomas Weise ist gelernter Anwendungsentwickler. Ihn zog es aber immer schon in Richtung Internet und Webentwicklung. Seine Artikel auf drweb sind Gastartikel, selbst bloggt er auf

24 Kommentare

  1. Hallo Thomas,
    vielen Dank für die sehr gut dokumentierten und hilfreichen scripts.
    Könntest Du mir bitte helfen, so daß ich die Testklasse01.php bzw. TwPdfRechnung.php so ändere, daß anstatt ein neues PDF zu erzeugen in eine bereits vorhandenes PDF-Dokument geschrieben werden kann ?
    Wäre Dir sehr dankbar.
    Interresant wäre auch Daten per PHP in ein PDF-Formular einzutragen.
    Wie kann ich mit PHP die Namen der PDF-Formularfelder abfragen und dann Daten reinschreiben ?
    Liebe Grüße
    Rolf

  2. @ Walter,

    ich meine damit, der ganze gesetztkonforme Rattenschwanz den man für eine papierlose Rechnungslegung betreiben musst, wird im Artikel gar nicht betrachtet. Und damit kann man den Artikel in die Tonne treten. Wenn man die Überschrift „PDF-Rechnung mit PHP erzeugen“ betrachtet, suggeriert es, ich kann damit meine Rechnungen als digitales Dokument erzeugen. Und das wars. Die obige Rechnung kannst Du nur einen Endverbraucher unterjubeln.

    Für den gewerblichen Bereich fehlt unteranderem:

    Das Einverständnis des Empfängers einholen. Er ist nicht verpflichtet, das elektronische Rechnungsverfahren zu akzeptieren.

    Es wird eine „qualifizierte Signatur“ benötigt.

    Die revisionsichere Archvierung für 10 Jahre gehört auch dazu.

    Der Empfänger ist ist verplichtet vor Geltungmachung der Vorsteuer, die Signatur der Rechnung zu überprüfen. Dies ist zu protokollieren. Andern falls könnte das Finanzamt die Rechnung nicht anerkennen, oder im Extremfall die Buchhaltung des Kunden verwerfen.

    Dies alles kann man als Dienstleistung einkaufen oder selbst vorhalten mit entsprechenden Kosten.

    Es wäre oben besser „PDF mit PHP erzeugen“ als Überschrift genommen worden. Der Author hätte mindestens den Hinweis geben können, diese PDF-Rechnung ist für die papierlose Abwicklung im Geschäftsleben nicht geeignet.

    Ich war auch mal der Meinung, Rechnung digital erzeugen ein Zertifikat basteln und schon kann man seine ganzen Rechnungen mit den Kunden digital abwickeln und man hat viel Geld und Arbeitszeit gespart.

    Um eine Rechnung auszudrucken brauch man kein PDF.

    nohope

  3. @ nohope:
    Meinst Du jetzt fiskalische Anforderungen bezüglich Angaben, die in der Rechnung enthalten sein müssen, wie fortlaufende Rechnungsnummer etc.? Die kann man ja selbst nachträglich einbauen – wie jede beliebige Zusatzangabe. Ich verstehe das Script als funktionierendes Grundgerüst, das jeder nach Bedarf verändert oder erweitert.

    Oder willst Du die PDF papierlos per E-Mail versenden und Dir fehlt die Signatur?

    LG walter

  4. Hallo,

    das mag ja vielleicht gut sein, um Rechnungen zuerstellen.
    Aber leider hat unser Fiskus andere Anforderungen an eine Rechnung per PDF. Mit so einer erzeugten Rechnung bin ich laut Gesetzt nicht berechtigt die Vorsteuer geltent zumachen.
    Und damit kann man das ganze in die Tonne treten, im gewerblichen Bereich.

    nohope

  5. Hab‘ meinen Fehler schon erkannt und behoben!

    LG, Walter

  6. Hallo Thomas,

    vielen Dank für Deinen Hinweis, er hat mir weiter geholfen.
    Jetzt habe ich noch eine Frage zum Problem von Elias, das Du ja schon gelöst hast. Ich verstehe nur die Lösung nicht richtig. Leider funktioniert es nicht, wenn ich an Stelle von
    $this->Cell(20, 5, sprintf(„%9.2f“, $this->twArrRechnungsdaten[‚rechnungsbetragNetto‘]), 0, 1, ‚R‘);
    folgendes einsetze:
    $this->Cell(20, 5, sprintf(„%9.2f“, $this->twArrRechnungsdaten[’str_replace(‚.‘, ‚,‘, strval(‚rechnungsbetragNetto‘))‘]), 0, 1, ‚R‘);
    Was mache ich falsch?

    Herzlichen Dank im voraus, Walter

  7. Habe mit Hilfe von twpdf ein Programm erstellt um online Rechnungen zu verwalten und zu erstellen.

  8. @Walter:
    Schau mal im Quellcode nach einer Zeile in der Art:
    $this->Cell(array_sum($this->twArrSpaltenbreiten), 0, “, ‚T‘); //Tabellenlinie unten

  9. Meine vorhergehende Frage hat sich erledigt. Ich belasse die 72 dpi Auflösung und erhalte für das Bild 300 dpi, wenn ich dessen Abmessungen in Millimetern definiere.

    Aber ich habe eine andere Frage:
    Wie bekomme ich eigentlich die Rahmen im multicell-Bereich weg? Für die Headerzeile war es kein Problem, aber ich finde die Zeile nicht, in welcher der Rahmen für die ganzen Array-Einträge abgeschaltet werden kann.

    Ich bitte um einen Hinweis.
    Walter

  10. Ganz herzlichen Dank, für die Scripte!
    Eine Frage habe ich dazu noch: Typischerweise werden Grafiken/Logos in druckbaren PDF-Dateien ja mit 300 dpi ausgegeben, um knackige Bildeindrücke zu erzeugen. Ich hinterlege also ein ganzseitiges logo.png (210 x 297 mm) mit 300 dpi Auflösung). Damit das passt, habe ich den Skalierungsfaktor in der fpdf.php auf 300 dpi geändert (siehe unten) und das Seitenformat entsprechend um den Faktor 4,167 länger und breiter gemacht:

    //Scale factor
    if($unit==’pt‘)
    $this->k=1;
    elseif($unit==’mm‘)
    $this->k=300/25.4;
    elseif($unit==’cm‘)
    $this->k=300/2.54;
    elseif($unit==’in‘)
    $this->k=300;
    else
    $this->Error(‚Incorrect unit: ‚.$unit);
    //Page format
    $this->PageFormats=array(‚a3’=>array(3507.88,4960.62), ‚a4’=>array(2480.33,3507.88), ‚a5’=>array(1753.92,2480.33),

    Das A4-Hintergrundbild (Logo) füllt die A4-Seite exakt aus. Dummerweise aber glaubt der AcroReader immer noch, dass die PDF-Datei nur eine 72dpi-Auflösung hat und behauptet, sie wäre 875 x 1273 mm groß (genau um den o.g. Faktor 300/72, also 4,167 vergrößert).
    Leider bin ich kein Programmierfreak, aber offenbar muss noch an einer anderen Stelle ein Wert um genau diesen Faktor geändert werden. Aber wo befindet sich die Zeile, in der diese Änderung erforderlich ist. Abschließender Hinweis: Auch die Schrift ist noch an 72 dpi angepasst und dementsprechend im Verhältnis zur realen Seitengröße 4,167 mal zu klein.

    Vielen Dank vorab und beste Grüße
    Walter

  11. Hallo Thomas,

    dieser Beitrag hat mehr sehr geholfen, vielen Dank für Deine Mühe!

    mfG.
    Gerd

  12. Mal so auf die Schnelle:
    str_replace(‚.‘, ‚,‘, strval(‚DER_BETRAG_MIT_PUNKT‘));

    Das müsstest du überall dort einfügen, wo der Betrag mit Punkt in Komma geändert werden soll.
    Das könnte man zwar auch noch besser in die Klasse gleich integrieren, aber so kannste dir erstmal helfen… ;-)

  13. Hallo,

    ein echt gutes Skript! Jedoch eine kleine Anregung sowie eine Frage: Auf Rechnungen schreibt man normalerweise Kommas und keine Punkte, kann ich dies schnell irgendwie ändern?

    Grüße

  14. @Andy: Ein Parameter, um die maximale Größe zu beschränken ist mir für FPDF nicht bekannt. Wie groß sind denn deine Bilddateien?

    @Tanja: Das „Umlautproblem“ kannst du mit utf8_decode() lösen.
    Beispiel:
    $this->blabla = utf8_decode(„AäöüßB“);
    (FPDF ansich kann kein utf8, dafür ist es aber auch noch nicht so aufgebläht)

    @Marcus: Mit SetFont() kannst du angeben, ob die Schrift fett oder normal sein soll. Verschiedene Zellen hintereinander kannste in der Art erstellen:
    $this->SetFont(‚Arial‘,“,’10‘);
    $this->SetXY(50, 50);
    $this->Cell(50, 5, utf8_decode(„blabla“), 0, 1, ‚R‘);
    $this->SetFont(‚Arial‘,’B‘,’10‘);
    $this->SetXY(100, 50);
    $this->Cell(50, 5, utf8_decode(„blub“), 0, 1, ‚L‘);

  15. Hallo,

    erstmal vielen Dank für die tolle Klasse – sowas habe ich schon lange gesucht.
    Ich habe die Klasse jetzt an meine Bedürfnisse angepasst (mein PHP ist nicht so toll – vorallem was Klassen angeht) und es funktioniert auch schon ganz gut.

    Jetzt möchte ich das ganze aber ein bischen erweitern und hänge ein bischen.

    Ich würde gerne haben, dass der Artikelname in fett gerdruckt wird und die „Erklärung“ also im Groben die weiteren Zeilen des Artikels in normaler Schrift geschrieben wird. Leider gibt das ja FPDF nicht her in einer Zelle das zu machen. Ich müsste quasi eine Zelle mit Fettschrift davor einfügen und dann erst die weiteren Zeilen habe aber keine Ahnung wie man das machen könnte.

    Kann mir bitte jemand behilflich sein?
    Vielen Dank im Voraus!

  16. tolle Skripte hast Du.

    Nur ich stehe irgendwie auf dem Schlauch. Ich würde gern aus einer Datenbank nur z.B. Artikel-Nr., Beschreibung + Menge auslesen und diese dann als eine Art Katalog ausdrucken.

    Ich habe es mit fpdf versucht und MultiCell, jetzt macht er mir zwar Zeilenumbrüche aber schreibt den Text in die Zeile des nächsten Datensatzes, wie kann ich das ändern, dass die Y-Koordinate anders bestimmt wird

    Habe auch Probleme, dass er mir Umlaute nicht anzeigt. Hast Du hier auch eine Losung. Mit decode habe ich schon probiert aber funktioniert nicht. Vielleicht setze ich es aber auch an die falsche stelle.
    Gruss
    Tanja

  17. Hallo,

    kurze Frage oder besser zwei Fragen….

    Warum werden die Dateien so groß? Ich habe 3 Bilder dazu gefügt und schon bin ich bei 17,4MB.

    Kann man im PDF Script eine maximale Größe angeben?

    Gruß Andy

  18. @Christian: Falls dein Problem nur mit dem Internet Explorer auftritt, les bitte mal folgendes:
    http://progtw.de/blog/tutorial-twpdf02-rechnung/ (die Kommentare)

  19. Hallo,
    wirklich interessantes Thema. Hab das gleich mal ausprobiert. Die Skripte laufen wohl und es wird etwas generiert nur leider keine PDF, eher ein Zeichensalat als Text im Browser ausgegeben. Von einer richtigen PDF fehlt jede Spur.
    Was mache ich hier falsch?
    Grüße

  20. Als Alternative zu FPDF kann man sich auch mal TCPDF anschauen: http://www.tcpdf.org :)

    Bügelt ein paar Mängel von FPDF aus, wie UTF-8 Support, transparente PNG-24 usw.

  21. Danke für diese detaillierte Anleitung. Ich habe gestern alles probiert und es hat sofort funktioniert! Jetzt passe ich gerade die Werte in der Rechnung auf meine Bedürfnisse an. Es macht Spaß.
    Danke!

  22. Eine wirklich feine Sache! Lässt sich auch gut für das eine oder andere „problem“ umfunktionieren…

  23. Tausend Dank für diesen Artikel – die Erklärungen erscheinen nachvollziehbar, probier ich am Wochenende mal aus. Schön ist, dass diese Lösung auch mit mehrseitigen Rechnungen funktioniert.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.