PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


Archive for Januar, 2010

Kein Hexenwerk: SMS/MMS mit PHP versenden

with 14 comments

Ziemlich kompliziert? Ziemlich teuer? Ich zeige euch das Gegenteil an einem kurzen Beispiel.

Ohne hier Werbung machen zu wollen werde ich in meinem Beispiel die Schnittstellen von www.sms77.de nutzen. Es gibt da draußen hunderte andere Anbieter, die vielleicht besser oder günstiger sind. Falls ihr einen Favoriten habt, nur her damit. Ich habe vor Jahren ein Konto bei SMS77 eingerichtet und nutze es hier und da für wichtige SMS-Benachrichtigungen.

(Nein, dies ist kein Werbeposting, ich bekomme (leider) kein Geld dafür.)

Also, um Nachrichten ins Mobilfunknetz verschicken zu können benötigt man einen Zugang zum Mobilfunknetz. Da man sich eine solche Umgebung nicht selbst aufbauen möchte, nutzt man sogenannte SMS-Gateways. Diese Anbieter haben eine Infrastruktur, um auf verschiedenen Wegen Nachrichten in das Mobilfunknetz zu leiten. Das sind neben SMS auch MMS, Logos, Klingeltöne und Wap-Push-Nachrichten.

Jedes SMS-Gateway bietet verschiedene Schnittstellen, wo wir die Nachrichten einliefern können. Die beliebteste ist wohl die HTTP-API, es gibt aber auch ein Email2SMS-Gateway, man kann die Nachrichten auch via Desktop-Anwendung versenden oder mit Hilfe eines Java-Programms, das man sich auf dem Handy installiert. Andere Provider bieten auch SS7, SMPP, XML etc an. Also mehr als genug Möglichkeiten, seine Information auf den Weg zu bringen.

Die ersten beiden Möglichkeiten möchte ich hier aus PHP heraus vorstellen. Wenn wir eine SMS versenden möchten, gibt es da noch verschiedene Qualitätsstufen und Typen. Dazu gibt es ganz unten im Artikel eine Übersicht.

Zuerst wollen wir eine einfache SMS versenden. Wir wählen den Typ BasicPlus und die HTTP-API:

$text = 'Hallo Admin, der Cronjob um 17 Uhr ist wider Erwarten nicht gelaufen. Bitte nachgucken!';
$url = 'http://sms77.de/gateway/?' .
		'u=' . urlencode('phpgangsta') .
		'&p=' . urlencode('md5passworthier') .
		'&to=00491771234567' .
		'&text=' . urlencode($text) .
		'&type=basicplus';
// Zur Vorsicht lieber ein @ davor, damit im Fehlerfall
// und falscher php-config die URL nicht publiziert wird
$response = @file_get_contents($url);
// nun noch $response auswerten und Rückgabecode prüfen
if ($response == '100') {
	echo 'Alles wunderbar, SMS ist versendet worden';
} else {
	echo 'Ein Fehler ist aufgetreten: '.$response;
}

Nun möchten wir eine Quality-SMS versenden. Diese soll zeitversetzt morgen früh um 3:00 versendet werden. Wir werden hier eine verschlüsselte Verbindung zu SMS77 aufbauen, einen Absender werden wir auch festlegen. Zu guter Letzt prüfen wir noch den Status der SMS:

$text = 'Hallo Admin, ab ins Bett, es ist 3 Uhr!';
$url = 'https://sms77.de/gateway/?' .
		'u=' . urlencode('phpgangsta') .
		'&p=' . urlencode('md5passworthier') .
		'&to=00491771234567' .
		'&text=' . urlencode($text) .
		'&type=quality' .
		'&status=1' .
		'&delay=' . strtotime('29.01.2010 3:00:00') .
		'&from=' . urlencode('PHPGangsta');
// Zur Vorsicht lieber ein @ davor, damit im Fehlerfall
// und falscher php-config die URL nicht publiziert wird
$response = @file_get_contents($url);
// nun noch $response auswerten und Rückgabecode prüfen
$responseData = explode("\n", $response);
if ($responseData[0] == '100') {
	echo 'Alles wunderbar, SMS ist versendet worden, die
			Msg-ID ist '.$responseData[1]."\n";
	sleep(10);
	$url = 'https://gateway.sms77.de/status.php?' .
		'u=' . urlencode('phpgangsta') .
		'&p=' . urlencode('md5passworthier') .
		'&msg_id=' . $responseData[1];
	$statusResponseData = @file_get_contents($url);
	$statusResponse = explode("\n", $statusResponseData);
	echo 'Status ist: ' . $statusResponse[0] .
			' um ' . date('d.m.Y H:i:s', $statusResponse[1]);

} else {
	echo 'Ein Fehler ist aufgetreten: '.$response;
}

Als Absender kann man eine Zeichenkette (bis 11 Zeichen) wählen oder eine Rufnummer. Des weiteren erhalten wir nun eine zweizeilige Antwort (durch status=1 ausgelöst), und zwar den ReturnCode und eine Message-ID, die wir dann zum Beispiel nutzen können, um den Status abzufragen.

Dies ist nur ein kurzer Beispielcode, in einem Produktivsystem sollte man das ganze natürlich noch etwas ausbauen, beispielsweise werden Verbindungsprobleme nicht abgefangen, und auch den Status sollte man in einer Schleife prüfen falls die SMS ein paar Sekunden länger benötigt.

Weitere Details findet man bei SMS77 in der API-Beschreibung. Darin stehen auch Einzelheiten zu Logos, Klingeltönen, MMS usw.

Nun noch schnell die Email-Schnittstelle an einem Beispiel erklärt:

include 'Zend/Loader/Autoloader.php';
$autoloader = Zend_Loader_Autoloader::getInstance();

$mail = new Zend_Mail('UTF-8');
$mail->setFrom('absender@phpgangsta.de')
	->addTo('email2sms@sms77.de')
	->setSubject('Hey Admin, Deine Webseite ist tot!')
	->setBodyText('email2smsKey#00491771234567#basicplus');

$config = array(
	'auth' => 'login',
	'username' => 'smtpusername',
	'password' => 'smtppasswort'
);
$transport = new Zend_Mail_Transport_Smtp('smtp.provider.de', $config);
// alternativ kann natürlich auch die gute alte mail() Funktion genutzt werden
// $transport = new Zend_Mail_Transport_Sendmail();
$mail->send($transport);

Es ist also nur eine normale Email mit bestimmten Angaben im BodyText, und schon kommt die SMS Sekunden später auf dem Handy an.

Das sind nur die Basics, es gibt noch viele weitere interessante Möglichkeiten, wie beispielsweise Inbound-SMS (SMS-Empfang, 3 Keywords bekommt man kostenlos), SMS-Daueraufträge, FreeSMS, Adressbuch (Automatische Geburtstags-SMS, oder statt Nummer einfach den Namen angeben beim Versenden) und einigem mehr.

Die Preise bewegen sich derzeit bei 3,5 Cent für eine BasicPlus-SMS und 7,9 Cent für eine Quality-SMS. Preise für Festnetz-SMS, MMS etc. entnimmt man der Webseite von SMS77.

Einfach mal ausprobieren, es gibt keine Einrichtungsgebühr, kein Mindestumsatz oder sonstwas, 2 Euro draufladen und Spass haben!

—————–

SMS77 unterscheidet zwischen

  • BasicPlus SMS: SMS wird über innerdeutsche Routen verschickt, Absenderadresse ist die Gatewayadresse von SMS77. Der Empfänger kann antworten, die Antwort ist bei SMS77 einsehbar (also eine anonyme Kommunikation)
  • Quality SMS: Man kann eine beliebige Absender-Nummer angeben. Damit könnte man eine falsche Nummer angeben, oder jedoch seine richtige Nummer, damit die Antwort auf dem eigenen Handy ankommt.
  • Flash SMS: Die meisten neuen Handys unterstützten diesen Typ. Die SMS wird direkt auf dem Bildschirm angezeigt, sie muss nicht erst geöffnet werden.
  • Festnetz SMS: Die SMS wird ins Festnetz verschickt und dort dann vorgelesen.

Written by Michael Kliewe

Januar 28th, 2010 at 9:21 am

Posted in PHP

Tagged with , , ,

„Meinten Sie“: Eingaben verbessern mit levenshtein() und soundex()

with 13 comments

Wer kennt es nicht: Wenn man in ein Suchfeld einen Suchbegriff eingibt, sich dabei jedoch vertippt oder es keine Suchergebnisse gibt, bekommt man manchmal einen alternativen Suchenbegriff vorgeschlagen, der mehr Ergebnisse liefert oder den falsch eingegebenen Begriff berichtigt. „Meinten Sie vielleicht“ oder „Did you mean“.

Doch wie funktioniert das?

Am einfachsten erledigt man diese Aufgabe mit der PHP-Funktion simliar_text() . Dieser Funktion übergibt man 2 Strings und erhält einen Ähnlichkeitswert, der die Anzahl der gleichen Buchstaben beschreibt. Alternativ kann man auch einen Prozentwert erhalten. Problem bei dieser Funktion: Sie ist langsam, die Laufzeit ist kubisch (n³).

Die aktuellere Funktion heißt levenshtein() und berechnet die Levenshtein-Distanz. Diese Distanz ist die minimale Anzahl an Einfüge-, Lösch- und Tausch-Operationen, um einen String in einen anderen zu verwandeln. Die Laufzeit dieser Funktion ist quadratisch (n²), allerdings gibt es auch Probleme: Es werden nur Strings mit maximal 255 Zeichen unterstützt. Falls auch längere Strings verglichen werden sollen, empfehle ich die Kommentare auf der PHP-Seite, denn dort stehen Quelltexte zum Nachbauen der Levenshtein-Distanz in PHP ohne diese Beschränkung, dort steht auch die Berechnung der prozentualen Gleichheit. Man sollte sich allerdings im Klaren sein, dass PHP-Code natürlich langsamer ist als die native PHP-C-Funktion.

Die levenshtein()-Funktion ist übrigens case-sensitiv, man sollte also überlegen die Parameter vorher in Kleinbuchstaben umzuwandeln (strtolower()).

Um noch bessere Ergebnisse oder Performance zu erhalten kann man auch die folgenden Funktionen hinzuziehen:

  • soundex() – Berechnung der Laut-Ähnlichkeit eines Strings. Mit Hilfe dieser Funktion kann man also bestimmen, ob sich 2 Worte ähnlich anhören! Für die deutsche Sprache ist die Funktion nicht ideal, in den Kommentaren stehen Verbesserungen und Alternativen. Ausprobieren!
  • metaphone() – Tut das selbe wie soundex(), doch kennt diese Funktion die Besonderheiten der englischen Aussprache. Besser ist jedoch DoubleMetaPhone, zu finden in den Kommentaren (lieber die PECL-Extension nutzen als die PHP-Klasse, um Performance zu steigern).
  • Die MySQL-Funktionen SOUNDEX() und „SOUNDS LIKE„, mit denen man die Berechnung der Datenbank überlassen kann.

Tolle Möglichkeiten, seine Suche zu verbessern und „fuzzy-like“ zu machen. Einziges Problem ist die Performance.

Written by Michael Kliewe

Januar 21st, 2010 at 8:52 am

Einen LDAP-Server/ActiveDirectory administrieren mit Zend_Ldap

with 2 comments

Ein Directory-Server zur zentralen Administration von Benutzeraccounts, Adressbüchern und anderen Resourcen aller Art ist eine feine Sache, sobald man mehrere Rechner, Dienste und/oder Benutzer zu administrieren hat. Nicht nur im Firmenumfeld, auch zu Hause haben IT-Spezies mehr und mehr einen LDAP (Leightweight Directory Access Protocol)-kompatiblen Server, der die genannten Informationen zentral zur Verfügung stellt.

Ohne hier detailliert auf die Vorteile eingehen zu wollen, kann man zum Beispiel viele Dienste mit den zentralen Benutzeraccounts auf dem LDAP-Server verbinden (evtl. mittels Samba, aber das soll hier nicht Thema sein), und so braucht sich ein Benutzer nur ein Passwort merken und dieses regelmäßig ändern anstatt für jeden Dienst einen eigenen Account zu haben. Ein Benutzer kann sich dann beispielsweise an jedem Rechner der Firma anmelden, sein Email-Kennwort ist das selbe, der FTP-Zugang ist auch automatisch verfügbar mit dem selben Kennwort, es können Rechte zentral verwaltet werden, und im Falle des Ausscheidens des Mitarbeiters muss man nur einen Account löschen und er hat nirgends mehr Zugriff. Natürlich können auch alle Intranet-Webseiten dann diese Login-Informationen nutzen, sodass nicht auf jeder Intranetseite ein separater Account angelegt und verwaltet werden muss (Stichwort Zend_Auth_Adapter_Ldap). Soweit die Theorie.

Auch ist es möglich, sein Adressbuch auf einem LDAP-Server zu speichern und dieses dann von überall nutzen zu können: via Webapplikation, Thunderbird, Outlook, Mail.app, wohlmöglich ein Software-IP-Telefon und und und. Das geht zum Beispiel nicht mit einer Datenbank.

Es gibt im Großen und Ganzen 2 bekannte Produkte auf dem Markt: OpenLDAP und ActiveDirectory von Microsoft. Beide Dienste bieten die oben genannten angerissenen Möglichkeiten und lassen sich via LDAP abfragen und manipulieren.

Nun zu PHP und Zend_Ldap: Wir können uns also zum LDAP-Dienst verbinden und dort neue Objekte anlegen, diese editieren, löschen, verschieben und vieles mehr. Im Folgenden zeige ich Euch am Beispiel von einem OpenLDAP-Server, wie man das sehr einfach machen kann.

Noch einige Grundlagen, damit der Code auch verständlicher ist:

  • Wurzel: Die Wurzel eines LDAP-Servers ist häufig eine Organisation (o) oder eine Domain (wird dann via dc benannt)
  • ou: Organisational Unit, wird zur Strukturierung (vgl. „Ordner“) genutzt, sodass sich ein Baum bildet
  • dn: Distinguished Name, das ist der vollständige Pfad zu einem Objekt
  • objectclass: Ein Objekt gehört mindestens einer (strukturellen) Klasse an, die die Attribute bestimmt und soetwas wie eine Schablone ist
  • … weitere Informationen finden sich im entsprechenden LDAP-Wikipedia Eintrag.

Die Baumstruktur sieht dann beispielsweise so aus:

PHPLdapAdmin

Ein Objekt in diesem Baum sieht dann zum Beispiel so aus:

Bild von Wikipedia

Und so greifen wir dann darauf zu:

$options = array(
    'host'              => '10.12.13.14',
    'username'          => 'cn=michael,ou=Users,dc=test,dc=de',
    'password'          => '123456',
    'bindRequiresDn'    => true,
    'accountDomainName' => 'test.de',
    'baseDn'            => 'dc=test,dc=de',
);

$ldap = new Zend_Ldap($options);
$ldap->bind();

Wir übergeben dem Konstruktor also die nötigen Daten und authentifizieren uns (bind()). Danach können wir, wenn die Authentifizierung geklappt hat und wir die entsprechenden Rechte in den OUs haben, Daten abfragen, zum Beispiel ein bestimmtes Objekt holen:

$user1 = $ldap->getEntry('cn=user1,ou=michael,ou=Addressbook,dc=test,dc=de');
/* array(
 *    'dn'          => 'cn=user1,ou=michael,ou=Addressbook,dc=test,dc=de',
 *    'cn'          => array('user1'),
 *    'givenname'   => array('Klaus'),
 *   ....
 */

Ein Objekt verändern ist auch sehr einfach:

$user1 = $ldap->getEntry('cn=user1,ou=michael,ou=Addressbook,dc=test,dc=de');
Zend_Ldap_Attribute::setAttribute($user1, 'displayName', 'User Eins');
$ldap->update('cn=user1,ou=michael,ou=Addressbook,dc=test,dc=de', $user1);

Ein neues Objekt fügen wir folgendermaßen hinzu:

$user2 = array();
Zend_Ldap_Attribute::setAttribute($user2, 'cn', 'UserZwei');
Zend_Ldap_Attribute::setAttribute($user2, 'objectClass', 'inetorgPerson');
$ldap->add('cn=UserZwei,ou=michael,ou=Addressbook,dc=test,dc=de', $user2);

Ein Objekt löschen könnte einfacher nicht sein:

$ldap->delete('cn=UserZwei,ou=michael,ou=Addressbook,dc=test,dc=de');

Suchen können wir natürlich auch nach verschiedenen Kriterien, beispielsweise so:

$filter1  = Zend_Ldap_Filter::begins('cn', 			$searchString);
$filter2  = Zend_Ldap_Filter::begins('sn', 			$searchString);
$filter3  = Zend_Ldap_Filter::begins('gn', 			$searchString);
$filter4  = Zend_Ldap_Filter::contains('displayname', 	$searchString);
$filter5  = Zend_Ldap_Filter::contains('mail', 			$searchString);

$filter = Zend_Ldap_Filter::orFilter($filter1, $filter2, $filter3, $filter4, $filter5);
$results1 = $ldap->searchEntries($filter);
$results2 = $ldap->search($filter);

search() und searchEntries() unterscheiden sich nur im Rückgabewert. Einmal erhalten wir ein Zend_Ldap_Collection Objekt und einmal ein einfaches Array mit den Ergebnissen.

Damit sollten die oft gebrauchten Anwendungsfälle abgedeckt sein. Weitere Informationen gibt es natürlich im Manual des Zend Frameworks zu Zend_Ldap.

Written by Michael Kliewe

Januar 18th, 2010 at 5:02 pm

Posted in PHP

Tagged with , , ,

Zwei Variablenwerte tauschen

with 13 comments

Gestern bin ich auf eine interessante Aufgabe gestossen. Eigentlich war es ein sehr einfacher Test bei einem Bewerbungsgespräch an einen Software-Entwickler-Kandidaten. Die Aufgabe lautete:

„Du hast 2 Variablen mit 2 Werten belegt. Tausche die beiden Werte schnell und effektiv aus.“

Die Erwartung war: Wenn der Kandidat nicht sofort anfängt, eine temporäre dritte Variable zu erstellen, hat er sehr wenig Programmiererfahrung.

$a = 5;
$b = 13;
$c = $a;
$a = $b;
$b = $c;

Doch wenn man genauer überlegt, gibt es viele weitere Lösungen, um die Aufgabe zu bewältigen, auch ohne eine dritte Variable. Hier ein paar schöne Lösungen:

$a = 5;
$b = 13;
$a += $b;
$b = $a - $b;
$a -= $b;

Bei dem obigen Beispiel gab es eine kleine Diskussion, ob ein Integer-Overflow Probleme machen könnte, dem ist aber nicht so.

$a = 5;
$b = 13;
$a ^= $b;
$b ^= $a;
$a ^= $b;

Tricky, aber funktioniert: mit der XOR Funktion klappt es wunderbar. Achtung: Hier wir das bitweise XOR verwendet, nicht das logische XOR.

Aber aufgepasst: Die obigen zwei Lösungen funktionieren nur mit ganzzahligen Werten! Stünden in den Variablen Strings, Objekte, Arrays oder sonstwas drin, würden die Lösungen scheitern. Und streng genommen ist in der Aufgabenstellung nirgends von Zahlen die Rede.

Hier noch ein Beispiel, das mit nur einer Zeile auskommt und jegliche Werte tauschen kann:

$a = 5;
$b = 13;
list($a, $b) = array($b, $a);

—————-
Edit:
Lösung von Tyco aus den Kommentaren:

$a = 5;
$b = 13;
$b = $b + $a – ($a = $b);

—————-

Es gibt also auch Lösungen, die ohne eine temporäre dritte Variable auskommen. Von der Lesbarkeit und Wartbarkeit sind die erste und die vierte Lösung wohl auch vorn.

Wer kann noch weitere schöne Lösungen beisteuern?

Written by Michael Kliewe

Januar 14th, 2010 at 8:49 am

Die große PHP-Blog-Sammlung

with 42 comments

Ein Bekannter fragte mich letztens, wieviele halbwegs aktuelle deutsche PHP-Blogs es gibt, die auch etwas Niveau haben, und ich konnte nicht anders antworten als „Pff, öhh, gute Frage, ich würde schätzen X, aber ich selbst kenne nur 5 davon“.

Problematisch sind bei diesen Kriterien vor allem „halbwegs aktuell“ und „etwas Niveau“. Es gibt in diversen Link-Katalogen sicherlich einige Links zu finden, aber viele davon bringen einen zu Blogs, die seit vielen Monaten oder Jahren nicht mehr aktualisiert werden. Genauso findet man Blogs, die von PHP-Neulingen gestartet wurden und das Lernen von PHP dokumentieren. Nichts gegen diese Blogs, sie haben absolut ihre Daseinsberechtigung, aber wenn man selbst die Sprache halbwegs beherrscht, möchte man solche Blogs eigentlich nicht verfolgen.

Also, wieviele PHP-Blogs gibt es? Und welche?

Voraussetzungen:

  • es muss ein Blog in deutscher Sprache sein
  • mindestens 1 Artikel über PHP alle 2 Wochen
  • der Blog muss mindestens 3 Monate alt sein

Ich möchte hier eine kleine Sammlung starten mit Links, die ich hier auflisten werde. Schreibt also in den Kommentaren die Links, eventuell auch das Alter, Spezialitäten usw.  Ihr dürft also etwas Werbung machen.

Falls nach 3 Wochen meine 5 Favoriten noch nicht genannt wurden, werde ich sie selbst vorschlagen und ergänzen. Aber es geht ja darum, alle anderen kennenzulernen, die „man“ noch nicht kennt! Also her mit den Links!

_________________________________________________________

Liste deutscher PHP-Blogs (alphabetisch sortiert):

Entsprechen (noch) nicht ganz allen Kriterien:

Written by Michael Kliewe

Januar 11th, 2010 at 12:16 pm

Posted in PHP

Tagged with , , ,