Archiv für die Kategorie "PHP"

Einem WCF Dienst aus PHP ein Dictionary als Parameter übergeben

Einem WCF Dienst ein Dictionary (hier Dictionary<string, string>) als Parameter zu übergeben, kann z.B. sinnvoll sein, wenn der Dienst eine Suche in einer SQL-Datenbank durchführen soll, wobei die SQL-Abfrage, die dieser Suche zu Grunde liegt, beliebig viele SQL-Parameter enthalten kann:

SELECT
    *
FROM
    [TABLE]
WHERE
    FIELD1 LIKE @Search1
    FIELD2 LIKE @Search2
    FIELD3 LIKE @Search3
    ...
    FIELDN LIKE @SearchN

Die entsprechende Methode des Dienstes könnte dann so aussehen (die Deklaration als void um so einfach wie möglich zu bleiben):

public void SendDictString(Dictionary<string, string> dParam)
{
    SqlCommand sc = new SqlCommand();
    sc.CommandText = ...; // Die SQL-Abfrage, z.B. aus Datei geladen

    Dictionary<string,string>.Enumerator e = dParam.GetEnumerator();
    while (e.MoveNext())
    {
        Debug("{0}:{1}", e.Current.Key.ToString(), e.Current.Value.ToString());

        sc.Parameters.Add(new SqlParameter(e.Current.Key, e.Current.Value));
    }
    SqlDataReader sdr = sc.ExecuteReader();
}

Betrachtet man die resultierende WSDL-Datei, sieht diese so aus:

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://schemas.microsoft.com/2003/10/Serialization/Arrays" elementFormDefault="qualified" targetNamespace="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<xs:complexType name="ArrayOfKeyValueOfstringstring">
<xs:annotation>
<xs:appinfo>
<IsDictionary xmlns="http://schemas.microsoft.com/2003/10/Serialization/">true</IsDictionary>
</xs:appinfo>
</xs:annotation>
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" name="KeyValueOfstringstring">
<xs:complexType>
<xs:sequence>
<xs:element name="Key" nillable="true" type="xs:string"/>
<xs:element name="Value" nillable="true" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:element name="ArrayOfKeyValueOfstringstring" nillable="true" type="tns:ArrayOfKeyValueOfstringstring"/>
</xs:schema>

Interessant an dieser Beschreibung ist dieser Teil:

<xs:sequence>
<xs:element name="Key" nillable="true" type="xs:string"/>
<xs:element name="Value" nillable="true" type="xs:string"/>
</xs:sequence>

Mit einer PHP-Anwendung ließe sich der Dienst entsprechend wie folgt konsumieren:

$client->SendDictString(array(
	"dParam"	=>	array(
		array(
			"Key"	=>	"Search1",
			"Value"	=>	"Suchbegriff1"
		),
		array(
			"Key"	=>	"Search2",
			"Value"	=>	"Suchbegriff2"
		),
		array(
			"Key"	=>	"Search3",
			"Value"	=>	"Suchbegriff3"
		)
	)
)

Die Debug-Ausgabe des WCF Dienst sieht dann so aus:

Search1:Suchbegriff1
Search2:Suchbegriff2
Search3:Suchbegriff3
Share
Geschrieben von Philip Ehret am 15.07.2011 | Kommentare weiterlesen...

Fehlermeldung „Error Fetching http headers“ bei der Abfrage eines WCF Dienstes, welcher einen benutzerdefinierten Typ zurückgibt

Bei der Entwicklung eines WCF WebService, definierte ich den folgenden ServiceContract:

[ServiceContract]
public interface IClient
{
    ...
    [OperationContract]
    WebUser GetUserData(string sWebUserHash);
    ...
}

Beim Abruf einer Implementierung des Dienstes mit Hilfe des PHP SoapClient kam es zu der Exception:

Error Fetching http headers

Zwar hat mich die Suche nach dem Ursprung unter anderem zu einem PHP Bug Report geführt, jedoch konnten die dort angegebenen Hilfen mein Problem nicht beheben.

Da die Kompilierung des WebService ohne Probleme verlief, die Dienstbeschreibung per HTTP abrufbar war und andere Methoden ohne Probleme funktionierten, vermutete ich das Problem beim PHP SoapClient. Jedoch stellte sich dann heraus, dass ich vergessen hatte, dem WebService den Typ WebUser bekanntzumachen.

Das Problem löste sich durch die simple Ergänzung des folgenden Attributs:

[ServiceKnownType(typeof(WebUser))]

Der funktionierende Quellcode sah dann so aus:

[ServiceContract]
[ServiceKnownType(typeof(WebUser))]
public interface IClient
{
    ...
    [OperationContract]
    WebUser GetUserData(string sWebUserHash);
    ...
}
Share
Geschrieben von Philip Ehret am 30.06.2011 | Kommentare weiterlesen...

Google Analytics Alternative wegen rechtlicher Unsicherheit

Schon seit langem setze ich auf Projekt-Webseiten Google Analytics ein, weil es meiner Meinung nach ausreichende und übersichtliche Auswertungen für Webseiten-Zugriffe bietet.

Zwar bin ich erklärter Google-Fan und finde es total absurd, wie in letzter Zeit gegen Google vorgegangen wird (siehe StreetView etc.) während ähnliche Dienste von Microsoft (Bing) und anderen Anbietern nicht schlechtgeredet werden, jedoch bewegt man sich beim Einsatz von Google Analytics scheinbar auf einem rechtlich unsicheren Pfad.

Da Webseiten, die Analytics einbinden, Daten über Besucher (auch die IP-Adressen) an Google weiterleiten, ist das rechtlich nicht in Ordnung. Beim Einsatz von Analytics muss man also im schlimmsten Fall mit (teuren) Abmahnungen rechnen.

Deswegen habe ich mich auf die Suche nach einer Alternative gemacht und diese jetzt gefunden: Piwik, eine Open-Source Google Analytics Alternative, speichert nach einer Installation auf einem eigenen Server Daten unabhängig von Dritten und kann leicht so angepasst werden, dass die Analysierung der Besucherdaten mit Deutschem Recht vereinbar ist:

  • Nach der Installation von Piwik das Plugin AnonymizeIP aktivieren
  • Auf einer Datenschutz-Unterseite o.ä. der Webseite dem Benutzer die Möglichkeit bieten, sich von der Datenerfassung auszuschließen
Share
Geschrieben von Philip Ehret am 11.01.2011 | Kommentare weiterlesen...

MySQLDu in neuer Version 1.1

Meine kleine MySQL-Backup-Lösung MySQLDu gibt es in der neuen Version 1.1.

Neben zwei kleinen Änderungen ist die neu hinzugefügte FTP-Unterstützung interessant. Backups können jetzt automatisch auf einen FTP-Server hochgeladen werden.

MySQLDu 1.1 herunterladen

Share
Geschrieben von Philip Ehret am 16.08.2010 | Kommentare weiterlesen...

Automatisches MySQL-Backup per Mail

Wem seine Daten lieb sind, der sichert sie häufig. Ob Wöchentlich, täglich oder stündlich hängt dabei von der Änderungsrate ebendieser ab, völlig unabhängig davon ist aber, dass die manuelle Erstellung von Backups kostbare Zeit kostet, die anderweitig besser investiert wäre.

Bei Internetprojekten ist die frei verfügbare MySQL-Datenbank mittlerweile ein Standard, administriert wird sie meistens mit phpMyAdmin, einem ebenso kostenlosem Web-Interface. Wer MySQL-Datenbanken normalerweise sichert, der erstellt in phpMyAdmin ein Datenbank-Dump und lässt es sich wahlweise als Textdatei oder in einem Archiv verpackt liefern. Doch aufgrund der nötigen Autorisierung am Server dauert die Erstellung schon einige Zeit, wieso da nicht einfacher? Mit einem automatischen Backup zum Beispiel.

Mithilfe eines Cronjobs, dem Tipp, wie man mit wget ein PHP-Script aufruft und MySQLDu lässt sich einfach eine Backup-Lösung erstellen. Wie das geht wird im Folgenden erklärt.

Laden wir zunächst die aktuelle Version von MySQLDu (1.0) herunter und entpacken das Archiv auf den Webserver, auf dem auch die MySQL-Datenbank liegt.

Als nächstes löschen wir die Dateien testoutput.php, testmail.php, testfile.php, msdu.sql und das Unterverzeichnis dump/, da diese entweder zu Testzwecken dienen oder für die Sicherung per Mail nicht erforderlich sind.

Ganz wichtig ist, dass das Unterverzeichnis tmp/ durch den Webserver-Benutzer schreibbar ist (am Besten setzen Sie die Rechte auf 0777).

Jetzt erstellen wir eine neue Datei cronbackup.php mit folgendem Inhalt:

<?php
	define("MYSQLDU_MODE","mail"); // legt den Mail-Modus fest

	// Datenbankzugangsdaten
	define("MYSQLDU_DB","");
	define("MYSQLDU_USER","");
	define("MYSQLDU_PASSWORD","");
	define("MYSQLDU_HOST","localhost");	

	// Mail-Optionen
	define("MYSQLDU_MAIL_FROM","noreply@philip-ehret.de");
	define("MYSQLDU_MAIL_FROMNAME","MySQLDu V!version");
	define("MYSQLDU_MAIL_TITLE","MySQL dump of !database on !d.!m.!Y (!H:!i:!s)");
	define("MYSQLDU_MAIL_TEXT","Hi there, this mail contains your dump of database !database created by MySQLDu V!version on !d.!m.!Y (!H:!i:!s).\n".
		"See attached file!\n\n".
		"Thanks for using MySQLDu (http://philip-ehret.de/projekte/mysqldu/)\n".
		"See also mysqldump.php from Huang Kai (http://atutility.com/software/mysqldumpphp/) on which MySQLDu is based on\n".
		"This mail was automatically generated and sent by PHPMailer(http://phpmailer.worxware.com/)");
	define("MYSQLDU_MAIL_SENDTO","mail@philip-ehret.de");
	define("MYSQLDU_MAIL_ATTACHMENT_NAME","!server.!database.!date.dump.sql");

	require_once("mysqldu-1.0.php");
?>

Wichtig ist, dass Sie die Konstanten MYSQLDU_USER, MYSQLDU_HOST, MYSQLDU_PASSWORD, MYSQLDU_DB an die Zugangsdaten Ihrer MySQL-Datenbank anpassen und MYSQLDU_MAIL_FROM und MYSQLDU_MAIL_SENDTO passend setzen (Das erste ist die E-Mail-Adresse, die später als Absender der Backup-Mail erscheint, das zweite die E-Mail-Adresse, an die das Backup gesandt werden soll).

Die Konstanten MYSQLDU_MAIL_TITLE und MYSQLDU_MAIL_TEXT können Sie nach belieben anpassen, mögliche Platzhalter finden Sie in der mysqldu-1.0.php in der Variablen $MYSQLDU_REPLACE_ARRAY.

Rufen Sie das Script jetzt einmal mit Ihrem Browser auf. Die Ausgabe sollte bei Erfolg die folgende sein. Eventuell öffnet Ihr Browser auch einen Download-Dialog, die heruntergeladene Datei enthält dann die Ausgabe.

{"status":"success","text":"Mail was successfully delivered"}

Es handelt sich hierbei um eine JSON-codierte Ausgabe. Das ist deswegen so, damit andere Scripte, die das Backup-Script aufrufen könnten, die Rückmeldungen einfach verarbeiten können.

Sollten Sie eine andere Ausgabe erhalten, so sehen Sie sich den JSON-codierten Fehlertext an. Dieser gibt Rückschlüsse darauf, was falsch läuft. Überprüfen Sie die Schreibrechte für das Unterverzeichnis tmp/ und stellen Sie sicher, dass Sie die Datenbankzugangsdaten korrekt eingegeben haben.

Wenn die Ausgabe stimmt, gehen Sie in das E-Mail-Postfach der für MYSQLDU_MAIL_SENDTO angegebenen E-Mail-Adresse. Dort sollte bereits eine E-Mail auf Sie warten, welche die Datenbanksicherung als Anhang enthält.

Um das E-Mail-Backup jetzt auch noch vollautomatisch zu erhalten, führen Sie sich den Artikel zur Ausführung eines PHP-Scriptes mithilfe eines Cronjobs zu Gemüte, welcher nicht nur für Plesk seine Gültigkeit hat.

Meine persönlichen Datenbanken sind zum Großteil in Unicode, da das etliche Vorteile bietet, welche den gegenüber anderen Datencodierungen höheren Speicherverbrauch meiner Meinung nach durchaus rechtfertigen.
Sollten Sie eine andere Codierung haben und die Dump-Datei fehlerhaft sein, versuchen Sie das Problem zu beheben, indem Sie an den Anfang der cronbackup.php folgenden Code einfügen:

define("MYSQLDU_UTF8",0);
Share
Geschrieben von Philip Ehret am 13.08.2010 | Kommentare weiterlesen...

Mit Plesk einen PHP Cronjob einrichten

Wer mit Plesk einen Cronjob einrichten möchte, der ein PHP-Script aufruft, kann das ohne umständliche Aufrufe mit dem Befehl wget machen, welcher normalerweise ein Dokument von einer URL lädt und abspeichert. Der Trick hierfür ist die Verwendung der Option spider, welche die Abspeicherung unterdrückt:

wget http://anydomain.tld/script.php?action=dosomething --spider
Share
Geschrieben von Philip Ehret am 13.08.2010 | Kommentare weiterlesen...

Smarty Rekursion in einem Template

Es kommt öfters vor, dass man ein Array ausgeben möchte, ohne die Tiefe zu kennen. Baumstrukturen oder Verzeichnislistings sind Beispiele dafür. Mit PHP und anderen Skriptsprachen lässt sich eine solche Struktur leicht mithilfe von Rekursion ausgeben.

Ich persönlich benutze seit geraumer Zeit die Smarty Template Engine, welche die Möglichkeit der Rekursion in Templates leider bisher vermissen lässt.
Wer jedoch zwischen den Zeilen liest, findet im Smarty-Forum eine Lösung, diese Funktionalität trotzdem implementieren zu können.

1) Aktuelle Version des Plugins compiler.defun herunterladen (Kompatibel mit PHP4+)

2) Die heruntergeladene Datei compiler.defun.php in das Plugins-Verzeichnis von Smarty kopieren (smarty/libs/plugins/)

3) Folgende Template-Datei als test.tpl in das Smarty-Template-Verzeichnis (normalerweise templates/) erstellen, welche ein verschachteltes Array ausgeben soll:

<ul>
{defun name="testrecursion" list=$tree}
{foreach from=$list item=node}
<li><span>{$node.caption} ({$node.access_string})</span>{if $node.children}<ul>
	{fun name="testrecursion" list=$node.children}</ul>{/if}</li>
{/foreach}
{/defun}
</ul>

4) Zuletzt mit folgendem Quellcode eine Datei test.php erzeugen, welche Smarty instanziert und ein verschachteltes Array zuweißt:

<?php
	require_once 'smarty/libs/Smarty.class.php';
	$smarty = new smarty();
	$smarty->assign("tree",array(
		0=>array(
			"caption"=>"Hallo",
			"access_string"=>"hallo.html",
			"children"=>array()
		),
		1=>array(
			"caption"=>"Huhu",
			"access_string"=>"huhu.html",
			"children"=>array(
				0=>array(
					"caption"=>"Wuhu",
					"access_string"=>"huhu/wuhu.html",
					"children"=>array()
				),
				1=>array(
					"caption"=>"Bla",
					"access_string"=>"huhu/bla.html",
					"children"=>array(
						0=>array(
							"caption"=>"Waha",
							"access_string"=>"huhu/bla/waha.html",
							"children"=>array(
								0=>array(
									"caption"=>"Ariba",
									"access_string"=>"huhu/bla/waha/ariba.html",
									"children"=>array(
										0=>array(
											"caption"=>"Wabadu",
											"access_string"=>"huhu/bla/waha/ariba/wabadu.html",
											"children"=>array()
										),
										1=>array(
											"caption"=>"Wabadi",
											"access_string"=>"huhu/bla/waha/ariba/wabadi.html",
											"children"=>array(
												0=>array(
													"caption"=>"Hallo",
													"access_string"=>"huhu/bla/waha/ariba/wabadi/hallo.html",
													"children"=>array()
												)
											)
										)
									)
								)
							)
						)
					)
				),
				2=>array(
					"caption"=>"Wau",
					"access_string"=>"huhu/wau.html",
					"children"=>array()
				)
			)
		),
		2=>array(
			"caption"=>"Maeh",
			"access_string"=>"maeh.html",
			"children"=>array(
				0=>array(
					"caption"=>"Muh",
					"access_string"=>"maeh/muh.html",
					"children"=>array()
				)
			)
		)
	));
	$smarty->display("test.tpl");
?>

Die Ausgabe von test.php sieht dann wie folgt aus:
Ausgabe test.php

Share
Geschrieben von Philip Ehret am 10.08.2010 | Kommentare weiterlesen...

Tastenkombination / Tastaturkombination / Keyboard Shortcut abfangen und benutzerdefinierte Aktion ermöglichen

Vielleicht möchte man irgendwann einmal Tastenkombinationen / Shortcuts des Benutzers selbst verwerten und die Standardaktion des Browsers verhindern. Zum Beispiel wenn der Benutzer ein Dokument bearbeiten können soll: Beim Drücken der Tastenkombination STRG + S soll dieses direkt gespeichert und nicht der Speichern-Dialog des Browsers (Im Firefox sieht der so aus) geöffnet werden.

Ganz easy geht das mit jQuery und dem Javascript jQuery Hotkeys Plugin

(Für kompletten Code bitte die DEMO ansehen: http://files.philip-ehret.de/dev/examples/jquery/hotkeys_bind.php)

<script type="text/javascript" src="jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="jquery.hotkeys-0.7.9.min.js"></script>

Nach Einbinden der jQuery-Bibilothek und des Plugins fehlt nur noch wenig:

jQuery(document).ready(function($){
	$(this).bind('keydown', 'ctrl+s', function(e){
		e.preventDefault(); // Standardaktion verhindern
		$('#someform').submit();
	});
});

Eigentlich würde man jetzt wahrscheinlich noch erläuternde Worte erwarten, aber die gibt’s diesmal nicht, bei der Einfachheit sollte der Code des Beispiels selbsterklärend sein.

Share
Geschrieben von Philip Ehret am 27.11.2009 | Kommentare weiterlesen...

The script tried to execute a method or access a property of an incomplete object

Eben ist mir ein banaler Fehler unterlaufen, der mich jedoch ziemlich zum Haare Raufen gebracht hat.

Ich war dabei, Quellcode auszulagern, der unter anderem folgendes enthielt:

require_once("class_user.php");
session_start();
ob_start();
if(!is_object($_SESSION["user"])) $_SESSION["user"] = new user();

Nach einem Login wurde in der Session ein User-Objekt gespeichert mit Werten wie Nickname, Passwort, Gruppenzugehörigkeiten usw.

Die wenigen Codezeilen oben überprüften bei jedem Aufruf der Seite, ob ein User-Objekt in der Sitzung vorhanden war. Wenn nicht, wurde eins mit den Standardwerten (kein Nickname, kein Passwort, keine Gruppen) erstellt.

Nach Auslagern des Quellcodes spuckte PHP folgende Meldung aus:

The script tried to execute a method or access a property of an incomplete object.

Da ich zu faul zum Googeln war und es meiner Meinung nach auch überhaupt nicht sein konnte, dass es zu einem Fehler kam, da ich den Code nicht verändert, sondern nur ausgelagert hatte, verbrachte ich einige Minuten mit Debugging, bis ich dann nach erfolgloser Fehlersuche doch Google damit fütterte.

Die Lösung fand ich letztendlich hier:

Da die Klasse Foo erst nach dem Starten der Session eingebunden wird, kennt PHP die Klasse noch nicht, die sich in der Session-Variablen $_SESSION[‚object‘] befindet.
Auf mein Problem übertragen bedeutete das, dass dem PHP Interpreter die Klasse user noch nicht bekannt war, als er dazu angewiesen wurde, eine Instanz davon aus der Session zu überprüfen.
Den obigen Codezeilen nach wurde die Klassendefinition von user jedoch vor der Intialisierung der Session geladen.

Ein Blick auf den neuen ausgelagerten Code zeigte dann aber, dass ich die Reihenfolge der Session-Intialisierung und der Einbindung der Klassendefiniton vertauscht und somit den Fehler provoziert hatte.

session_start();
ob_start();
require_once("class_user.php");

Sollte also jemals der Fehler The script tried to execute a method or access a property of an incomplete object in einem Script vorkommen, so bietet sich an, als allererstes zu überprüfen, ob die Definition des Objektes vor dessen Intialisierung erfolgte!

Share
Geschrieben von Philip Ehret am 03.08.2009 | Kommentare weiterlesen...

file_exists unter Einbeziehung des Include Path

Wer unter PHP hin und wieder mit Dateioperationen zu tun hat, oder auch nur hier und da ‚mal wissen will, ob eine Datei, die eingebunden werden soll, auch wirklich existiert und dabei in mehreren Verzeichnissen danach Ausschau halten möchte, dem ist wahrscheinlich bewusst, wie lange PHP schon darauf warten lässt, dass file_exists() auch auf den Include Path angewendet werden kann.

Ein Eintrag in der PHP Bug Sektion offenbart, dass Entwickler schon Jahre auf dieses Feature warten:

Come on, let’s fix this in under 10 years!

Da ich in letzter Zeit öfters über diese Hürde gestolpert bin, habe ich jetzt eine eigene Funktion geschrieben, die file_exists auf den Include Path anwendet:

function file_exists_ip($filename) {
	if(function_exists("get_include_path")) {
		$include_path = get_include_path();
	} elseif(false !== ($ip = ini_get("include_path"))) {
		$include_path = $ip;
	} else {return false;}

	if(false !== strpos($include_path, PATH_SEPARATOR)) {
		if(false !== ($temp = explode(PATH_SEPARATOR, $include_path)) && count($temp) > 0) {
			for($n = 0; $n < count($temp); $n++) {
				if(false !== @file_exists($temp[$n] . $filename)) {
					return true;
				}
			}
			return false;
		} else {return false;}
	} elseif(!empty($include_path)) {
		if(false !== @file_exists($include_path . $filename)) {
			return true;
		} else {return false;}
	} else {return false;}
}

Vielleicht hilft sie anderen Suchenden da drausen im weiten Web und spart Ihre kostbare Zeit, etwas ähnliches zu kreieren.

Der Aufruf erfolgt genauso wie bei file_exists():

if(file_exists_ip($filename)) {
	//Tu dies
	//Tu das
}

und überprüft dabei alle Ordner, die mittels

init_set("include_path", "a/;b/c/d/;e/;f/g/h/;");

oder

set_include_path("a/;b/c/d/;e/;f/g/h/;");

als Include Path angegeben wurden.

Share
Geschrieben von Philip Ehret am 30.11.-0001 | Kommentare weiterlesen...