Archiv der Kategorie: PHP

Mein Beitrag zum Developers Shame Day

Mein Beitrag zum Developers Shame Day am 03.11.2010:

Oh, wie ich mich schäme, der Code ist von Oktober 2002, stammt aus einem Teil, der für ein Onlinerollenspiel vorgesehen war (damals gab es das Wort Browserspiel noch gar nicht – trotzdem wollte ich sowas bauen; leider war das Projekt damals viel zu groß für mich).

Im Großen und Ganzen habe ich allerdings grade dadurch, dass ich diesen Code geschrieben habe, davon genervt war, wieviel „Arbeit“ das immer wiederkehrende Tippen von < und > überdrüssig war und hiermit erste, zarte Schrittchen im Bereich PHP und Klassen getätigt habe überhaupt erst näher zu OOP in PHP 3+4 und Verwendung von Template Engines (direkt im Anschluss war es FastTemplate, kurz dannach Smarty … bis heute) … soviel zum Thema „sich weiterentwickeln durch schlechten Code“!

Hab sowas mal bei thedailywtf gesehen, hatte es aber in dem Moment doch schon zu sehr verdrängt, als dass da was geklingelt hätte.


class pageproducer
{
function getDoctypeInfo()
{
return "\n\n";
}

function getStartingTag($tagname)
{
return "<".$tagname.">\n";
}

function getEndingTag($tagname)
{
return "\n";
}

function getMetaTagEquiv($name,$content)
{
return "";
}

function getTitle($name)
{
return "".$name."";
}

function getCSS($cssurl)
{
return "";
}

function getHeader()
{
$header = $this->getMetaTagName( "keywords", "") . "\n";
$header .= $this->getMetaTagName( "description", "") . "\n";
$header .= $this->getMetaTagName( "robots", "") . "\n";
$header .= $this->getMetaTagName( "revisit-after", "") . "\n";
$header .= $this->getMetaTagEquiv( "Content-Type", "text/html; charset=iso-8859-1") . "\n";
$header .= $this->getMetaTagEquiv( "expires", "0") . "\n";
$header .= $this->getTitle(HEAD_NAME) . "\n";
$header .= $this->getCSS("rpg.css") . "\n";
return $header;
}

function getHeadSection()
{
$head = $this->getStartingTag("head");
$head .= $this->getHeader();
$head .= $this->getEndingTag("head");
return $head;
}


}

Ich persönlich finde das Konstrukt wirklich schlimm und oberpeinlich; ich komme also heute mit Papiertüte über dem Kopf zur Arbeit 😉 Aber was soll’s, mittlerweile sieht mein Code nicht mehr so aus; mittlerweile übergebe ich das Charset als Parameter #muhaha 😉

Mal ernsthaft: Ich finde die Idee des Ganzen „Developers Shame Day“ sehr gut. Gut für Newbies, dass auch wir „Pros“ mal „Newbies“ und „Script-Kiddies“ waren; gut für uns „Pros“, um unsere eigenen Wurzeln zu reflektieren und nicht zu vergessen.

DRY – redundante Funktionen in PHP vermeiden

DRY – Don’t repeat yourself.
Das kennt jeder, das _sollte_ jeder wissen. Und so gut wie jeder, der etwas Ahnung hat, versucht es so oft es geht umzusetzen.

Allerdings: Das gilt nicht nur für Funktionsnamen, das DRY-Prinzip geht weiter. Es meint IMHO auch, dass man für viele ähnliche Abfragen sich am besten Wrapper baut, die dann die Anfrage an eine Funktion leiten, die die eigentliche Antwort produziert. Die Wrapper „füttern“ die eigentliche Funktion nur mit den richtigen Fragen.

Ein Beispiel: Siggi Scriptkid hat ein Klasse zur Benutzerverwaltung geschrieben, die braucht er in seinem aktuellen Projekt. Er speichert seine Benutzerdaten in einer MySQL Datenbank und muss nun seiner Klasse folgende Fragen stellen und erwartet folgende Antworten:

  • Hole mir den Benutzer mit der ID 5
  • Hole mir den Benutzer mit der Mailadresse xyz
  • (noch viele weitere, ähnliche Fragen mehr)

Siggi schreibt also ganz fleißig Funktionen in seine Klasse:
(Stark vereinfacht, es fehlen Filtern der Usereingaben, maskieren der SQL String und vieles mehr; der Code dient nur als Beispiel!)


class User
{
public function getUserByID($userid)
{
$ressource = mysql_query('SELECT vorname, name
FROM user
WHERE userid = '.$userid);
$user = mysql_fetch_assoc($ressource);
return $user;
}

public function getUserByMailadress($email)
{
$ressource = mysql_query('SELECT ID, mail, userlevel
FROM user
WHERE mail = "'.$email.'"');
$user = mysql_fetch_assoc($ressource);
return $user;
}
}

Nun trifft man auf folgendes Problem: Die Felder der jeweiligen Rückgabe sind unterschiedlich. Auch wenn Siggi nun hingeht, und in beiden Funktionen die Felder gleich setzt, ergeben sich Wartungsschwierigkeiten:

  • Was ist, wenn noch mehr, ähnliche Funktionen hinzukommen? Dann bekommt Siggi wirklich Arbeit!
  • Was ist, wenn Siggi der „user“ Tabelle noch mehr Felder hinzufügen muss und es 6 Funktionen gibt?
  • Was ist, wenn Siggi auch nur eine der Funktionen vergisst?

Um es kurz zu machen: Hier greift das DRY-Prinzip NICHT! Der o.a. Code sollte einem Entwickler, der nicht Siggi Scriptkid heißen möchte, nicht passieren.

Aber wie wende ich das DRY-Prinzip nun an?
Okay, ich schreibe mal meinen Vorschlag dazu:


class User
{
public function getUserByID($userid)
{
return $this->getUser('ID='.$userid);
}

public function getUserByMailadress($email)
{
return $this->getUser('mail='.$email);
}

private function getUser($where)
{
$ressource = mysql_query('SELECT ID, vorname, name, mail, userlevel
FROM user
WHERE '.$where.'
LIMIT 1);
$user = mysql_fetch_assoc($ressource);
return $user;
}
}

Ich schaffe eine private Methode, die die Daten aus der Datenbank holt. Diese ist die einzige Methode, die das macht, somit habe ich nur eine Stelle, in der ich zukünftig Felder ändern muss, wenn sich welche ändern.

Die „alten“ Funktionen verbleiben als Wrapper-Funktionen und übergeben der privaten Funktion nur die richtigen Parameter, um ihre eigene Aufgabe erfüllen zu können. Dann leiten Sie Antwort einfach als eigene wieder zurück.

Auf diesem Wege verinfache ich die Pflege, bin in der Lage, meine alten Funktionen weiterhin nutzen zu können – mit dem Vorteil, dass nun alle Antworten einheitlich sind – und bin darüberhinaus in der Lage, sehr schnell verschiedene neue Funktionen zu schreiben, die mit neuen Fragestellungen klarkommen, z.B. Benutzer mit E-Mail x und Passwort y.

Was haltet ihr von dieser Vorgehensweise. Ich selbst halte sie für praktiabel und gut, was meint ihr? Gut? Schlecht? Gibt es bessere Vorgehensweisen?

Arrayelemente zählen mit Smarty

Die Anzahl der Elemente eines Arrays in Smarty erhält man mit


{$myArray|@count}

oder mit


{@count($myArray)}

Damit lassen sich schöne Ausgaben zaubern, in dem man etwa prüft, ob ein Array Werte beinhaltet (z.B. nach einer DB-Abfrage) oder eben nicht und dann, je nachdem, die Ausgabe Benutzerfreundlich macht:


{if @count($myArray)>0}
Toll, es gibt {@count($myArray)} Einträge :)
{else}
Sorry, keine Einträge gefunden :(
{/if}

Die Angabe des @-Modifikators gibt an, dass nun eine PHP Funktion folgt. Intern wird also die PHP-Funktion count aufgerufen und dessen Rückgabe dann eingesetzt und benutzt.

Aber aufpassen: Wenn die $security Variable auf true steht, dann muss jede PHP-Funktion, die man so benutzen möchte, auch im $security_settings[‚MODIFIER_FUNCS‘]-Array stehen, sonst funktioniert der Aufruf nicht!

Quelle: Smarty-Doku, Kapitel 5 – Variablen Modifikatoren

Alles loggen, alles!

Ich stieß kürzlich auf diesen Artikel:
Log Everything All the Time
und fand diesen sehr interessant.

Es geht um’s mitloggen von Informationen und der damit verbundenen verbesserung der Debugfähigkeit von Software für dessen Entwickler. Das ganze klingt für mich sehr interessant, vor allem der Punkt, dass man (theorethisch) jeden Zeitpunkt zu jeder Zeit nachstellen kann – klingt sehr nach ZendServer 😉 – wäre für mich zumindest manchmal sehr wünschenswert.

Allerdings kann ich mich gut vorstellen, da so ein logging sehr an der Performance kratzt, aber der Autor gibt weiter unten im Artikel sehr nützliche Tipps, wie man das maximum herausholen kann, wenn man nur schon beim Design ein paar – wie ich finde fundemantal wichtige – Regeln beachtet, z.B. „Formatiere nichts, bis es nicht gebraucht wird“ oder „Nur ein Tabellenlookup pro Log“.

Von mir aus kommt da noch eins hinzu: „Benutze keine Frameworks!“. Au weia, jetzt hauen mich alle Zend-Freaks. Ich will damit sagen, dass beim Thema ‚logging‘ auch die Zeit eine sehr kritische Rolle spielt – wir reden hier ja nicht über ein oder zweimal loggen pro Aufruf, sondern jeden Request, in jeder Funktion, in jeder Klasse, quasi in jeder Zeile – und da zählt jede Nanosekunde. Frameworks haben diesbezüglich aber einen Nachteil: Sie haben Overhead.

Wie auch immer, ich würde gern eine Klasse für dieses Logging entwickeln und würde mich freuen, von euch Anregungen zu bekommen, was diese Klasse machen soll, wie Sie aufgerufen werden sollte (instanz, static, …) und wie und wo die Daten gespeichert werden sollten (spontan kommt mir da mysql in den Sinn, wegen durchsuchen usw.). Ich denke, wenn wir alle Ideen sammeln, können wir eine gute Logging Klasse entwickeln, die alle diese Informationen in einem guten Zeitrahmen erfasst und speichert. Das auslesen kann ja von mir aus wieder längern dauern, da kommt es nicht so auf die Zeit an.

*hm* Eventuell gibt es ja schon so eine Klasse, irgendwo, allein oder versteckt in einem Framerwork. In dem Fall bitte ich um einen kurzen Hinweis, dann kann ich mir die Arbeit sparen und direkt produktiv loggen.

Was denkt ihr über den Artikel? Was haltet ihr von so einer Klasse?

P.S.: Sorry, die Klasse soll primär für PHP sein, die Ideen natürlich für alle!

UTF Dateien mit PHP fopen erzeugen

Will Mensch eine „UTF-8“ Datei mit PHP’s eigener fopen-Funktion erzeugen, muss er nur ein paar Dinge beachten.

Zum einen muss der jeweilige Inhalt des fwrite-Befehl UTF-8 encoded sein; das erreicht man am sichersten über die Funktion mb_convert_encoding.

Dann erzeugt man das File auf jeden Fall mit dem ‚b‘ Parameter, sicher ist sicher.

Als letztes setzt Mensch in der Datei noch die entsprechende „Byte Order Mark“ hinein. Das passiert mit Hilfe des pack-Kommandos.

Letztendlich kann man dann seinen Content in die Datei schreiben.


$content = mb_convert_encoding($content,'UTF-8');
$fHandle = fopen($filename,'wb');
fwrite($fHandle, pack("CCC",0xef,0xbb,0xbf));
fwrite($fHandle, $content);
fclose($fHandle);

P.S.: Nicht das fclose vergessen, das wäre einfach schlechter Stil 😉

Keine UTF-8 Dateien in Windows

Windows erstellt Ordner mittels mkdir in UTF-8 nicht korrekt: Sonderzeichen werden nicht als solche behandelt, sondern als „ecoding-müll“ (är) dargestellt. Die Lösung: Das Encoding des Strings in der Funktion mkdir muss ISO sein.

Beispiel: Aus der DB kommt der Projektname ‚Hallo Bär‘, dieser steht im Array $projectData im Feld ’name‘ und liegt im UTF-8 Encoding vor.


echo mb_detect_encoding($projectData['name']);
$outputdir = $projectData['name'].DIRECTORY_SEPARATOR.date('YmdHis');
echo mb_detect_encoding($outputdir);
mkdir($outputdir,0755,true);
$outputdir2 = mb_convert_encoding($outputdir, 'iso-8859-15');
echo mb_detect_encoding($outputdir2);
mkdir($outputdir2,0755,true);
exit;

Ausgabe ist einmal, dass $outputdir UTF-8 ist, allerdings wird das VZ „Hallo Bär“ als „Hallo Bär“ erstellt, nach der Konvertierung gibt PHP zwar immer noch UTF-8 aus, allerdings wird das VZ nun korrekt erstellt.

Anmerkung: Das ist ein vereinfachter Beispielcode, nicht, dass es hinterher wieder heißt, der Sascha baut keinen schönen Code 😉

phpinfo mit Parameter

Was mir persönlich bisher ziemlich unbekannt war, ist die Tatsache, dass man phpinfo auch mit Parametern aufrufen kann.

Ruft man einfach

phpinfo();

auf, dann erscheint ganz normal die Übersicht über alle Features des Webservers. Allerdings ist es ja oft so, dass man aus diesem ganzen Wust von Informationen nur einen ganz kleinen Teil benötigt, zum Beispiel die Servervariablen. Dazu benutzt man den (optionalen) Parameter $what (der heißt wirklich so).

Um nur die Servervariablen zu bekommen, ruft man die Funktion einfach mit der richtigen benannten Konstante auf:

phpinfo(INFO_VARIABLES);

Hier mal eine Übersicht über alle Konstanten mit eigenen Erklärungen, die „Originaltexte“ könnt ihr unten beim Quellenlink nachlesen:

  • INFO_GENERAL
    Informationen über das System, build-config, stream-filter, usw.
  • INFO_CREDITS
    Die Danksagungen. In meinem Zend Community Server ist der bereich bis auf die Überschrift leer.
  • INFO_CONFIGURATION
    Die Konfiguration des aktuellen Systems. safe_mode on oder off, error_log Verzeichnis usw. findet sich hier.
  • INFO_MODULES
    Die aktivierten Module des Servers. bz2 an? curl enabled? gd Unterstützung mit dabei? Alles hier.
  • INFO_ENVIRONMENT
    Die Umgebungsvariablen des Servers; userprofile, os, path, usw.
  • INFO_VARIABLES
    Quasi das ganze $_Server-Array nochmal im phpinfo-theme.
  • INFO_LICENSE
    Die PHP Lizenz.
  • INFO_ALL
    Zeigt alle oben angegebenen Parameter an, die gleiche Ausgabe, wie ohne Parameter.

sehr gut ist auch, dass man diese Parameter miteinander verknüpfen kann, um mehr, aber doch noch nicht alle Informationen zu bekommen:

phpinfo(INFO_MODULES + INFO_VARIABLES);

Somit erhält man nur 2 Bereiche, in denen man die Informationen heraussuchen kann, die man benötigt.

Quelle: http://de2.php.net/manual/de/function.phpinfo.php

PHP Filter vs. Zend Framework am Fall einer Mailadressprüfung

Eine Mailadresse prüfen, dass muss jeder Entwickler mal in seinem Code machen. Dass dabei eine Menge schief gehen kann, davon kann sicherlich jeder PHP-Programmierer ein Lied singen. Gemacht werden muss es aber, nur wie? Ich stelle die PHP eigene Funktion filter_var() gegen die Filterfunktion des Zend Frameworks und schaue mal, welche der beiden Lösungen denn „Praxistaugliche“ Antworten erzeugt.

Für die PHP eigenen Funktionen lasse ich filter_var mit Filteroption FILTER_VALIDATE_EMAIL antreten. Zur einfachen Übersicht baue ich eine Funktion „isEMailValid“. Diese soll bei korrekter Mailadresse true zurückliefern, ansonsten false; also eher trivial.


function isEMailValid($mail)
{
return (filter_var($mail, FILTER_VALIDATE_EMAIL) !== FALSE);
}

Für die Frameworks tritt das Zend Framework an und speziell dessen Zend_Validate_EmailAddress Klasse. Diese besitzt eine Funktion isValid, die das gleiche Verhalten zeigt wie unsere eigene „isEMailValid“ Funktion.

Als Beispiel gebe ich 4 ähnliche Mailadressen an, nur die erste ist eine „echte“ Mailadresse, bei allen anderen fehlt was bzw. ist ein Teil unvollständig. Sicherlich prüft das ganze nicht alle möglichen Fälle von Mailadressen ab, aber es zeigt einen (sinngemäßen) Fall, der mir Probleme bereitete und mich nun zum Wechsel von meiner „filter_var plus regex“-Methode zum ZendFramework bewegen konnte.


function isEMailValid($mail)
{
return (filter_var($mail, FILTER_VALIDATE_EMAIL) !== FALSE);
}

$mail1 = 'max.muster-mann@eine.beispieldomain.com';
$mail2 = 'max.muster-mann@com';
$mail3 = 'max.muster-mann eine.beispieldomain.com';
$mail4 = 'max.muster-manneine.beispieldomain.com';

printf('php-filter:'."\n");
var_dump(isEMailValid($mail1));
var_dump(isEMailValid($mail2));
var_dump(isEMailValid($mail3));
var_dump(isEMailValid($mail4));

printf('zend:'."\n");
require_once 'Zend/Validate.php';
require_once 'Zend/Validate/EmailAddress.php';
$mailvalidator = new Zend_Validate_EmailAddress();
var_dump($mailvalidator->isValid($mail1));
var_dump($mailvalidator->isValid($mail2));
var_dump($mailvalidator->isValid($mail3));
var_dump($mailvalidator->isValid($mail4));

Die Ausgabe:


php-filter:
bool(true)
bool(true)
bool(false)
bool(false)
zend:
bool(true)
bool(false)
bool(false)
bool(false)

Wie man sieht, die „normale“ filter_var() Funktion von PHP schlägt fehl und meldet die zweite Mailadresse als korrekt, was nicht richtig ist.

Allein das zeigt mir nun, dass ich mich auf die Ergebnisse der PHP eigenen Prüfung nicht verlassen kann. Ich werde das ganze weiterhin beobachten, allerdings wird das nächste Projekt dann Funktionen aus dem Zend Framework benutzen. Das schöne ist, dass ich nur einmal in meiner Prüfungsmethode meiner Validatorklasse die Prüfung ändern muss, damit Projektweit die „richtige“ Prüfung benutzt wird. OOP ist toll 😉

PHP-Version: 5.2.13

Nachtrag vom 24.09.2010

Die ersten 5 Kommentare brachten mich auf andere Wege und zu mehr „Erkentnis“ des ganzen. Danke an IchBinIch und SkaveRat für die Hinweise!

Nimmt man noch ein paar andere Abwandlungen der Mailadressen zeigt sich ein Bild, dass man erklären sollte, denn filter_var und Zend arbeiten anscheinend völlig unterschiedlich. Oder doch nicht?

Was soll man nun benutzen? filter_var oder doch die Zend-Funktionen?

Die Antwortet lautet: Es kommt drauf an!
Je nachdem, was man mit der Prüfung erreichen will, benötigt man entweder das eine oder das andere. Hier die beiden Möglichkeiten:

  • 1. Prüfung für den Mailversand!
    Der wohl häufigste Fall. Man möchte wissen, ob eine Mailadresse dazu taugt, etwas per SMTP dort auch hinsenden zu können (Zugangsdaten, Newsletter, …). Hier lautet die Antwort: Zend-Framework, denn das prüft, ob ein String eine versendbare Mailadresse nach RFC 5321 beinhaltet (siehe auch Kommentar 3 von SkaveRat).
  • 2. Prüfung, ob es ganz generell eine Mailadresse sein könnte!
    Hier ist die Antwort: filter_var, denn diese Funktion prüft die generelle Syntax des Strings, nicht dessen Nutzbarkeit. Dabei können auch Global-Parts wie ‚localhost‘ eingesetzt werden, allerdings werden auch Mailadressen wie ‚a@com‘ durchgelassen, da diese ja theoretisch auch Mailadressen sind.

Man sieht, dass es durchaus auf den Anwendungsfall ankommt, will man sich für eine Filterklasse entscheiden. Ich werde das auf jeden Fall bei der nächsten Prüfung berücksichtigen.

PHP-Snippet: int2bin

Integer zu Binär casten:


function int2bin($number)
{
$number = filter_var($number,FILTER_SANITIZE_NUMBER_INT);
return ($number=='') ? FALSE : base_convert($number, 10, 2);
}

Eingabe: Eine Zahl oder ein String mit Zahlen darin.
Rückgabe: FALSE, wenn keine Zahl in $number gefunden wurde oder die entsprechende Binär-Ausgabe der Zahlen in §number.
Hinweis: $number=’A21′ erhält ‚10101‘ als Rückgabe!