Archive for the ‘PHP’ Category
Nicht-HTML-Responses mit dem Zend Framework
Wenn man dynamische Bilder oder RSS-Feeds oder einen AJAX/JSON-Service oder ein Excel-Export mithilfe des Zend Frameworks erstellen will, mußt man 2-3 wichtige Dinge beachten. Der Code soll dann in einem Rss-/Graph-/Ajax-/Export-Controller stehen.
Ein Problem bekommt man, wenn man ein Layout benutzt (Zend_Layout). Denn dann wird dieses Layout immer ausgegeben. Im hier betrachteten Fall wäre das aber sehr schädlich, denn dadurch würden wir unser Bild/RSS-Feed/AJAX/Excel-Response zerstören.
Unschön kann man das wie folgt lösen:
public function rssAction() { // calculate rss data and echo it (with correct headers) exit; }
Richtig und deutlich schöner ist das Abschalten des Layout in der Action, wie folgt:
public function rssAction() { // disable layout $this->_helper->layout()->disableLayout(); // disable view rendering $this->_helper->viewRenderer->setNoRender(); // calculate rss data and echo it (with correct headers) }
Wir schalten auch gleich noch den ViewRenderer mit aus, damit auch nicht versucht wird, ein Viewscript zu rendern (das es wahrscheinlich garnicht gibt).
Dieses RSS-Beispiel funktioniert natürlich genauso für die anderen Beispiele, wo kein klassischer HTML-Quelltext zurückgegeben werden soll, sondern eine Antwort in einem anderen Format gefordert ist.
Damit kann man dann seine dynmisch erstellten Bilder (z.B. mittels pChart, jpgraph oder direkt die GD-Funktionen/image* in php), RSS-Feeds (Zend_Feed), Ajax-Services (Zend_Json) usw. realisieren.
Hier noch schnell ein Beispiel eines Excel-Exports aus einer Datenbank, mit Hilfe der Spreadsheet-Klasse aus dem PEAR-Framework (vereinfacht auf das Wesentliche):
class ExportController extends Zend_Controller_Action { public function excel() { // disable layout $this->_helper->layout()->disableLayout(); // disable view rendering $this->_helper->viewRenderer->setNoRender(); // get some data from database here // create empty file //include 'Spreadsheet/Excel/Writer.php'; $excel = new Spreadsheet_Excel_Writer(); // add worksheet $sheet =& $excel->addWorksheet('Daily Export'); $sheet->setColumn(0,0,20); $sheet->setColumn(1,1,15); $sheet->setColumn(2,2,18); $sheet->setColumn(3,3,23); $sheet->setColumn(4,4,35); $sheet->setColumn(5,5,15); $format_bold =& $excel->addFormat(); $format_bold->setBold(); $format_headline =& $excel->addFormat(); $format_headline->setBold(); $format_headline->setSize(20); $format_headline->setAlign('center'); // headline $sheet->write(0, 0, 'Results: '.date('d.m.Y H:i'), $format_headline); $sheet->mergeCells(0,0,0,5); // add data to worksheet $rowCount=2; foreach ($data as $groupName=>$serverData) { $sheet->write($rowCount, 0, $groupName, $format_bold); $rowCount++; foreach ($serverData as $row) { $colcount = 0; foreach ($row as $key => $value) { $sheet->write($rowCount, $colcount, $value); $colcount++; } $rowCount++; } $rowCount++; } // send client headers $excel->send('daily_export_'.date("Ymd-His").'.xls'); } }
Dies hier ist alter Code, mittlerweile nutzen wir PHPExcel.
PHP 5.3 released!
Closures, Late Static Binding, Namespaces, neuer Mysql Native Driver, Garbage Collection und und und.
Wem das (noch) nichts sagt, sollte sich in den nächsten Tagen und Wochen damit beschäftigen (hier im Blog werde ich sicherlich auch einiges davon vorstellen), denn
PHP 5.3
wurde soeben offiziell released! http://www.php.net/downloads.php
Ich persönlich würde damit zwar noch nicht auf Produktiv-Systeme gehen, aber zuhause in Entwicklungsumgebungen und vielleicht auf kleinen Homepages kann man damit schon arbeiten denke ich. Wenn in 4-6 Wochen dann die ersten Bugfixes verfügbar sind, werden wir in der Firma sicher auch mal intensiver damit testen, um unsere alten Systeme auf kurz oder lang umzustellen. Das ist aber häufig ein monatelanger Prozess, wir werden sehen.
Da dies kein langer Post werden soll, kann ich nur sagen: Probiert es aus und testet die neuen Funktionen!
Firewall Beschränkungen prüfen
Zuhause hat man diese Probleme wohl nicht so sehr, und auch auf einem Rootserver braucht man sich nur sehr selten damit rumplagen: Firewalls, die den Zugriff auf andere Systeme blocken.
Firewalls sind im Prinzip nichts anderes als Programme (oder Hardware), die im Netzwerk oder auf einem System installiert sind und Netzwerkpakete (nicht nur TCP/IP, sondern auch UDP, ICMP Pakete usw) untersuchen kann. Jede Firewall hat Regeln, die beschreiben, was passieren soll wenn bestimmte Pakete ankommen. Zum Beispiel kann sie die Verbindung komplett unterbinden (d.h. die ankommenden Pakete droppen) wenn ein Quellsystem 1.2.3.4 das Zielsystem 6.7.8.9 auf Port 25 versucht zu erreichen. Das wäre ein Blacklist-Eintrag.
Sicherer und häufig einfacher ist aber eine Whitelist. Dann wird prinzipiell erstmal alles gedroppt, und nur einige definierte Verbindungen dürfen durchgelassen werden. Bei einer lokalen Firewall zuhause oder auf einem Server resultiert das in 1-20 Regeln. In einer größeren Firma mit komplexen Netzwerken, vielen vielen Servern und Arbeitsrechnern, einigen dutzend IP-Bereichen und Hardware-Firewalls, die ganze Netze voneinander trennen (also nicht hunderte lokal installierte Software-Firewalls), artet diese Sicherheit häufig auch in viel Arbeit und komplexe Regeln aus.
Kürzlich mußten wir ein System mit einer komplexen Software, welche Verbindungen zu aktuell 65 anderen Systemen (es werden kontinuierlich mehr) aufbaut, um dort Daten abzuholen und abzuliefern, von einem Server auf einen anderen Server in einem anderen Netz umziehen. Da wir keine Listen hatten, zu welchen Zielsystemen eine Verbindung möglich ist und zu welchen nicht, hilft nur ausprobieren.
Da wir Informatiker ja bekanntlich faul sind (was nicht immer schlecht ist, denn faule Programmierer meiden redundanten Code, programmieren selten mehr Schnick-Schnack in eine Anwendung als benötigt usw) haben wir uns ein kleines Script geschrieben, das alle benötigten Zielsysteme durchprobiert und eine schöne Liste der nicht erfolgreichen Verbindungen ausgibt.
Unsere erste Version tat genau das: Es versucht, alle Zielsysteme auf einem bestimmten Port zu erreichen, indem es eine Socketverbindung aufbaut und dann wieder trennt. Ein schicker 40-Zeiler:
<?php require_once('../init.php'); $configIni = Zend_Registry::get($configIni); echo 'We are using database: ' . DATABASE_DBNAME."\n\n"; $portChecks = array(); $portChecks[] = array('Host' => $configIni->mail->standard->host, 'Port' => $configIni->mail->standard->port); $portChecks[] = array('Host' => $configIni->mail->secure->host, 'Port' => $configIni->mail->secure->port); $portChecks[] = array('Host' => $configIni->db->config->hostnameonly, 'Port' => $configIni->db->config->port); $portChecks[] = array('Host' => $configIni->product1->host, 'Port' => $configIni->product1->port); $db = MDB2::singleton('mssql://' . DATABASE_USER . ':' . DATABASE_PASS . '@' . DATABASE_SERVER . ':' . DATABASE_PORT . '/' . DATABASE_DBNAME); $db->setFetchMode(MDB2_FETCHMODE_ASSOC); // add FTP Checks from DB $ftpModules = $db->queryAll('SELECT DISTINCT Server, Serverport FROM FTP'); foreach ($ftpModules as $ftpModule) { $portChecks[] = array('Host' => $ftpModule['server'], 'Port' => $ftpModule['serverport'] == 0 ? 21 : $ftpModule['serverport'] ); } // add SFTP Checks from DB $sftpModules = $db->queryAll('SELECT DISTINCT Server, Serverport FROM SFTP'); foreach ($sftpModules as $sftpModule) { $portChecks[] = array('Host' => $sftpModule['server'], 'Port' => $sftpModule['serverport'] == 0 ? 22 : $sftpModule['serverport'] ); } echo "\nStarting Port Tests:\n"; foreach ($portChecks as $portCheck) { $ret = @fsockopen($portCheck['Host'], $portCheck['Port']); if (!$ret) { echo 'Port ' . $portCheck['Port'] . ' on Host ' . $portCheck['Host'] . " (IP: ".gethostbyname($portCheck['Host']).") cannot be opened. Please check!\n"; } else { fclose($ret); echo "."; } }
In der zweiten Version kann es aktuell auch noch etwas detaillierter die eigentlichen Protokolle nutzen und sich zB auf einem FTP-Server einloggen oder SMB-Pfade prüfen. Das geht über den eigentlichen Porttest hinaus, ist aber für unsere Einsatzzwecke durchaus interessant, um zB falsche Login-Informationen oder anderweitige Probleme aufzudecken.
Erste Twitter Tests
Da Twitter in aller Munde ist, habe ich es mir am Wochenende nun auch mal angeschaut und mir einen Account eingerichtet. Zwar schon die letzten Monate des öfteres gehört und gelesen, konnte ich mich bisher nicht davon überzeugen lassen. Da auch keiner meiner Bekannten twittert, war der Anreiz nicht wirklich groß.
Jetzt am Wochenende habe ich einen Podcast vom Chaos Computer Club: Chaosradio Folge 147 gehört, der mich nun zu Experimenten angespornt hat.
Ein Account ist schnell erstellt, eine Statusnachricht geschrieben, und ein paar Leuten folge ich auch bereits.
Heute habe ich mir das WordPress-Plugin TwitterSuite installiert, mit dem ich hoffe, automatische Tweets zu senden bei neuen Beiträgen, Last Tweets anzuzeigen und einigem mehr. Dieser Post ist auch dazu da, diese Funktionalität mal zu testen. Ich hoffe, dass es funktioniert 😉
EDIT: Hmm, das ging wohl voll in die Hose. Habe wohl curl nicht installiert auf dem Server. *verschnauf* Hätte ja auch so einfach sein können.
Da ich jetzt weder Zeit noch Lust habe, probiere ich nun den twitter_updater.
EDIT2: Scheint zu funktionieren! *jubel*
Thumbnails erstellen mit PHP
Da es im Zend Framework keine schöne Klasse für diese Aufgabe gibt (Zend_Image gab es mal als Proposal, wurde aber abgelehnt), muss man anderweitig eine Lösung finden.
Ich habe dazu eine uralte Funktion, die aber nach wie vor fabelhaft funktioniert. Man übergibt einfach nur den Pfad zur Quelldatei (häufig ist das eine hochgeladene Datei, es kann sowohl ein jpg als auch ein gif sein), ein Zielverzeichnis (häufig ein Ordner im webroot), sowie Informationen über die Zieldatei: Dateiname, Endung, maximale Höhe und Breite.
Ein Beispiel würde so aussehen:
Util::createThumbnailAndSave($_FILES['uploadfile']['tmp_name'], 'images/uploads', $_FILES['uploadfile']['name'], 'jpg', 250, 250)
Danach schreibt man die Informationen über die Datei natürlich noch in eine Datenbank, damit man sie auf der Webseite einbinden kann.
Und natürlich möchte ich euch meine kleine Funktion nicht vorenthalten. Es wird kein Imagick benötigt, sondern nur eine „normale“ PHP-Installation incl. aktivierten GD-Funktionen.
public static function createThumbnailAndSave($filePath, $targetDir, $targetFilename, $targetExtension, $targetWidth, $targetHeight) { $imageinfo = getimagesize($filePath); // check if source image is smaller than target dimensions if ($imageinfo[0] > $targetWidth || $imageinfo[1] > $targetHeight) { if ($imageinfo[2] == 1) { // GIF $srcImg = imagecreatefromgif($filePath); } elseif ($imageinfo[2] == 2) { // JPG $srcImg = imagecreatefromjpeg($filePath); } $widthDivisor = $imageinfo[0] / $targetWidth; $heightDivisor = $imageinfo[1] / $targetHeight; if ($widthDivisor > $heightDivisor) { $dstImg = imagecreatetruecolor($targetWidth, $imageinfo[1] / $widthDivisor); } else { $dstImg = imagecreatetruecolor($imageinfo[0] / $heightDivisor, $targetHeight); } imagecopyresampled($dstImg, $srcImg, 0, 0, 0, 0, imagesx($dstImg), imagesy($dstImg), imagesx($srcImg), imagesy($srcImg)); // Save to harddisc and delete from RAM imagejpeg($dstImg, $targetDir . "/" . $targetFilename . "." . $targetExtension, 95); $fsize = filesize($targetDir . "/" . $targetFilename . "." . $targetExtension); if ($fsize > 50000) { imagejpeg($dstImg, $targetDir . "/" . $targetFilename . "." . $targetExtension, 60); } imagedestroy($dstImg); imagedestroy($srcImg); } else { copy($filePath, $targetDir . "/" . $targetFilename . "." . $targetExtension); } chmod($targetDir . "/" . $targetFilename . "." . $targetExtension, 0777); }
Eigentlich könnte ich sie mal auf Aktualität überprüfen, heutzutage würde ich sie wahrscheinlich auch etwas anders schreiben. Aber für meine paar kleinen privaten Homepages reicht es. Alternativen sind natürlich fertige Klassen von phpclasses.org etc,