Nov 222011
 

Ohne viel Worte, einfach mal ein kleines Snippet – just use it :)

    /**
     * Create an array from all protected properties and returns it.
     *
     * @return array
     */
    public function toArray()
    {
        $reflectionObj = new ReflectionClass(get_class($this));
        $vars          = $reflectionObj->getProperties(ReflectionProperty::IS_PROTECTED);
        $array         = array();

        foreach (new RecursiveArrayIterator($vars) as $property) {
            $name = $property->getName();
            $tempPropertyName  = trim($name, '_');
            $tempPropertyArray = explode('_', $tempPropertyName);

            $propertyName      = '';
            foreach ($tempPropertyArray as $part) {
                $propertyName .= ucfirst($part);
            }

            $array[lcfirst($propertyName)] = $this->$name;
        }

        return $array;
    }
 

Des öfteren wird es mal benötigt, dass Elemente eines HTML-Quellcodes oder sogar ganze Tabellen via Remote ausgelesen und verarbeitet werden.

Als Beispiel möchte ich von meinem Xerox Phaser 6140N die aktuellen Toner-Füllstände auslesen. Sollten in einem Netzwerk mehrere Drucker vorhanden sein, so könnte man das ganze per Cronjob automatisieren und zum Beispiel eine eMail versenden. Man behält immer den Überblick und kann sich rechtzeitig darüber informieren lassen, sobald der Füllstand eines Gerätes zu niedrig wird.

Ich habe meinem Beispieldrucker also eine fixe IP-Adresse verpasst (172.16.1.254). Klicken wir uns über Status zu den Füllständen, können wir im Firefox “nur diesen Frame anzeigen” auswählen. Die URI dazu könnte so aussehen:

https://172.16.1.254/status/statsuppliesx.htm

Der Quellcode dieser Seite ist folgender:

<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<link rel="stylesheet" href="../common.css" type="text/css">

<title>Phaser 6140N - 172.16.1.254</title>
</head>
<body><div id=frameR>

<table border="0" cellpadding="0" cellspacing="0" width="100%" class=s3 ><tr><td align="center" nowrap><font class=s3><a href="statgeneralx.htm"><img src="../images/optunpressed.gif"  width=19 height=18 border=0 align="bottom" alt=""> Allgemeines</a></font></td><td align="center" nowrap><font class=s3><a href="statsuppliesx.htm"><img src="../images/optpressed.gif"  width=19 height=18 border=0 align="bottom" alt=""> Verbrauchsmaterialien</a></font></td></tr></table><font class=s5>Verbrauchsmaterialstatus</font><br>

<div align=right><a href="http://www.office.xerox.com/cgi-bin/printer.pl?APP=CWIS&Page=Color&Model=Phaser+6140&PgName=Order&Language=german" target="_blank"><font class=stdfnt>Verbrauchsmaterialien bestellen</font></a> </div>
<table width=100% class=textarea >
<tr><td class=caption  colspan=3>Tonerkartuschen</td></tr>
<tr style="font-weight: bold"><td width=50% align=left nowrap>Farbe</td><td align=left>Stand</td><td align=left></td></tr><tr><td colspan=3 width=100% class=hrline></td></tr>
<tr>
<td align=left nowrap>Cyan</td>
<td align=left>100 %</td>
<td align=left><img src="../images/ca100.png" width=32 height=32 border=0 alt=""></td>
</tr>
<tr>

<td align=left nowrap>Magenta</td>
<td align=left>100 %</td>
<td align=left><img src="../images/mg100.png" width=32 height=32 border=0 alt=""></td>
</tr>
<tr>
<td align=left nowrap>Gelb</td>
<td align=left>100 %</td>
<td align=left><img src="../images/yl100.png" width=32 height=32 border=0 alt=""></td>
</tr>
<tr>
<td align=left nowrap>Schwarz</td>
<td align=left>100 %</td>

<td align=left><img src="../images/bk100.png" width=32 height=32 border=0 alt=""></td>
</tr>
</table><br>

<table width=100% class=textarea >
<tr><td class=caption  colspan=3>Wartungselemente</td></tr>
<tr style="font-weight: bold"><td width=50% align=left nowrap>Belichtungseinheit</td><td align=left>Stand</td><td align=left></td></tr><tr><td colspan=3 width=100% class=hrline></td></tr>
<tr>
<td align=left nowrap>Belichtungseinheit</td>
<td align=left>100 %</td>
<td align=left></td>
</tr>

</table><br>

</div></body>
</html>

Wie ihr bereits sehen könnt, befinden sich in diesem Quellcode die benötigten Daten in einer Tabelle:

<td align=left nowrap>Magenta</td>
<td align=left>100 %</td>

Da wir die Daten über das Netzwerk abrufen, benötigen wir entsprechende Funktionen. Ich habe mich hier für CURL entschieden, da ich hier die meisten Variations-Möglichkeiten habe.

Setzen des Links zu den Drucker-Tonerfüllständen sowie angeben einiger Optionen:

<?php
$h = curl_init('https://172.16.1.254/status/statsuppliesx.htm');
curl_setopt($h, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($h, CURLOPT_HEADER, 0);
curl_setopt($h, CURLOPT_HTTPGET, 1);
curl_setopt($h, CURLINFO_HEADER_OUT, 0);
curl_setopt($h, CURLOPT_RETURNTRANSFER, 1);

Damit wir keine Probleme mit dem HTTPS-Zertifikat bekommen, setzen wir CURLOPT_SSL_VERIFYPEER auf false. In einem lokalem Netz dürfte das kein Problem in Punkto Sicherheit darstellen.

Nun möchten wir unsere HTML-Daten empfangen:

$html = curl_exec($h);
curl_close($h);

Wir gehen jetzt davon aus, dass $html den Quellcode der Website wie oben dargestellt enthält. Sollte $html vom Typ bool sein – also false zurück liefern, so muss eine Fehlerüberprüfung statt finden, welche nicht Teil dieses Beitrages sein wird.

Spezifisch für unsere Seite können wir nun ein DOM-Dokument aus dem Quellcode erzeugen:

$dom = @DOMDocument::loadHTML($html);
$dom->preserveWhiteSpace = false;
$tables = $dom->getElementsByTagName('table');
$rows = $tables->item(1)->getElementsByTagName('tr');

Weil $html auch invalides Markup führen kann, haben wir ein @-Zeichen vorran gesetzt. Dies unterdrückt die Ausgabe von möglichen Parser-Fehlern. White Spaces werden entfernt und $rows beinhaltet letzten Endes unsere tr-Tags samt Inhalt. Diese können wir nun durchlaufen und die benötigten Werte abspeichern. Diese können dann wie bereits zu Beginn erwähnt per eMail verschickt oder in eine Datenbank geschrieben werden:

$result = array();

foreach ($rows as $row)
{
    $cols = $row->getElementsByTagName('td');

    if (is_object($cols->item(1)) && substr($cols->item(1)->nodeValue, -1) == '%') {
        $temp = array();
        $temp['color'] = $cols->item(0)->nodeValue;
        $temp['fillLevel'] = $cols->item(1)->nodeValue;
        $result[] = $temp;
    }
}

print_r($result);

Die letzte Zeile würde folgende Ausgabe erzeugen:

Array
(
    [0] => Array
        (
            [color] => Cyan
            [fillLevel] => 100 %
        )

    [1] => Array
        (
            [color] => Magenta
            [fillLevel] => 100 %
        )

    [2] => Array
        (
            [color] => Yellow
            [fillLevel] => 100 %
        )

    [3] => Array
        (
            [color] => Black
            [fillLevel] => 100 %
        )

)

Wir haben also per CURL unseren Tonerfüllstand des Netzwerkdruckers ausgelesen.

 

Wer kennt es nicht – das PDF?

Wir haben es alle bestimmt schon mal benutzt. Doch wie erstellen wir ein solches Dokument? Ich möchte euch in diesem kleinem Tutorial zeigen, wie ihr mit der Zend-PDF-Komponente eure eigenen PDF-Dateien erstellen könnt. Der Clou dabei: wir verwenden Zeilenumbrüche (mehrzeilige Texte).

Zum Schluss möchten wir in unserem Controller nur wenig Code sehen. Dieser könnte so aussehen:

<?php
class PdfController
    extends Zend_Controller_Action
{
    public function init()
    {
        $this->_helper
             ->viewRenderer
             ->setNoRender(true);
        $this->_helper
             ->layout()
             ->disableLayout();
    }

    public function createPdfAction()
    {
        // @todo hier evtl. Daten aus einem Model/Service holen und an My_Example_Pdf übergeben
        // My_Example_Pdf könnte genauso gut auch gleich ein Service sein.
        $pdf = new My_Example_Pdf();
        $pdf->renderToOutput();
    }
}

Hierzu benötigen wir jedoch zunächst die Klasse My_Example_Pdf.

<?php
class My_Example_Pdf
    extends Zend_Pdf
{
    public function renderToOutput()
    {
    }
}

So kommen wir unserem Ziel schon näher – langsam ;D Fangen wir hier also mit der Ausgabe der PDF-Datei an. Dafür müssen bestimmte Header gesetzt werden. Darunter zB. das PDF als Body und die Content-Length.

$response->setHeader('Content-Disposition', 'attachment; filename="My_Example_PDF_' . date('d-m-Y') . '.pdf"');
$response->setHeader('Content-Type', 'application/pdf', true);
$response->setHeader('Content-length', strlen($binaryPdf));
$response->setBody($binaryPdf);

$binaryPdf entspricht hier dem Inhalt des gerenderten PDF-Dokumentes, den wir durch Aufruf der render-Methode der Zend_Pdf-Klasse erhalten.

$binaryPdf = $pdf->render();

Wir können ein PDF natürlich komplett ohne irgendwelche Templates erstellen. Ich für meinen Fall, habe jedoch eine Example.pdf-Datei, welche bereits feste Formularfeldpositionen hat, die später ausgefüllt werden sollen. Es gäbe nun verschiedene Ansätze unsere PDF-Datei mit Daten zu füttern. Ich entscheide mich hier für den Weg, bereits vorab eine Instanz von Zend_Pdf zu erzeugen und diese an meine My_Example_Pdf-Klasse zu übergeben. Je nachdem in welchem Kontext wir uns gerade befinden und wie spezifisch das PDF aufgebaut werden muss, könnten sich auch andere Ansätze erschließen.

Erzeugen wir also eine Instanz von Zend_PDF und laden unser Tempalte Example.pdf, welches wir nun auch mit Daten füllen werden:

$pdf = Zend_Pdf::load(APPLICATION_PATH . '/../data/Example.pdf');
$firstPage = $pdf->pages[0];
$firstPage->setFont(Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_HELVETICA_BOLD), 10);
$firstPage->drawText('foo', 105, 764.3, 'UTF-8');
$firstPage->drawText('bar', 181.8, 740.3, 'UTF-8');
$firstPage->drawText('baz', 365, 740.3, 'UTF-8');
$firstPage->setFont(Zend_Pdf_Font::fontWithName(Zend_Pdf_Font::FONT_HELVETICA), 9);
$this->drawMultilineText($firstPage, $something, 75, 685);

Huch, was haben wir denn hier? Die Funktion drawMultilineText haben wir bisher noch gar nicht erwähnt. Das holen wir nun nach. Diese Funktion erwartet als ersten Parameter eine Pdf-Seite, auf welcher mehrzeiliger Text eingefügt werden soll. Als zweiten Parameter übergeben wir den Text, als dritten und vierten Parameter die x- und y-Startpositionen von unten links ausgehend, als 5ten Parameter können wir die Zeilenhöhe und als 6ten das Charset angeben. Durch Angabe der Zeilenhöhe können wir also – aha das ist unser Geheimnis – mehrere Zeilen berechnen und entsprechend den Text hinein zeichnen. Die Zeilenhöhe sollte daher entsprechend der Schritgröße oder etwas größer gewählt werden.

Nun zu unserer Funktion:

    public function drawMultilineText(Zend_Pdf_Page $pdfPage, $text, $xStart, $yStart, $lineHeight=10, $charset='UTF-8')
    {
        $i = 0;

        $string_a = explode("n", $text);
        $outputString = '';
        foreach ($string_a as $part) {
            $temp = wordwrap($part, $this->_maxLen, "n", true);
            $temp_a = explode("n", $temp);

            foreach ($temp_a as $line) {
                $i++;
                if ($i > $this->maxLines) {
                    break;
                }

                // Nun Zeichnen wir eine Zeile von unserem Text.
                $pdfPage->drawText($this->_spaces . $line, $xStart, $yStart, $charset);

                $yStart -= $lineHeight;
            }
        }
    }

Wir teilen unseren Text anhand der bereits vorhandenen Zeilenumbrüche, iterieren über den Text, brechen die Zeilen nochmals anhand der maximalen Zeichenlänge um und iterieren weiter – nur diesmal über die zerstückelten Satzteile bis wir endlich eine neue Zeile in unsere PDF-Seite dazu zeichen. Wir haben also nachstehende Klasse erzeugt:

class My_Example_Pdf
    extends Zend_Pdf
{
    /**
     * You can add space before every line for advanced usage.
     *
     * @var string
     */
    private $_spaces = '';

    /**
     * The maximum char count per line.
     *
     * @var integer
     */
    private $_maxLen = 110;

    /**
     * The maximum of rows to draw.
     *
     * @var integer
     */
    private $_maxLines = 40;

    public function __construct($options = null)
    {
        // Nothing to do here for now.
    }

    public function renderToOutput()
    {
        $binaryPdf = $pdf->render();

        $response->setHeader('Content-Disposition', 'attachment; filename="My_Example_PDF_' . date('d-m-Y') . '.pdf"');
        $response->setHeader('Content-Type', 'application/pdf', true);
        $response->setHeader('Content-length', strlen($binaryPdf));
        $response->setBody($binaryPdf);
        $response->sendResponse();
    }

    public function drawMultilineText(Zend_Pdf_Page $pdfPage, $text, $xStart, $yStart, $lineHeight=10, $charset='UTF-8')
    {
        $i = 0;

        $string_a = explode("n", $text);
        $outputString = '';
        foreach ($string_a as $part) {
            $temp = wordwrap($part, $this->_maxLen, "n", true);
            $temp_a = explode("n", $temp);

            foreach ($temp_a as $line) {
                $i++;
                if ($i > $this->maxLines) {
                    break;
                }

                // Nun Zeichnen wir eine Zeile von unserem Text.
                $pdfPage->drawText($this->_spaces . $line, $xStart, $yStart, $charset);

                $yStart -= $lineHeight;
            }
        }
    }
}

Wir können nun das $firstPage-Objekt an My_Example_Pdf->drawMultilineText($firstPage, …. übergeben und danach unser PDF rendern und zB. zum Download anbieten.

© 2010-2012 RenePardon BoonWeb Suffusion theme by Sayontan Sinha