PHP aus JavaScript heraus aufrufen
- javascript
- php
Hallo zusammen,
ich bin ein erfahrener Windows-Entwickler und habe mich vor längerer Zeit etwas in die Web-Programmierung eingearbeitet (Html, PHP, JavaScript, CSS). Dabei habe ich u.a. für mein Windows-Projekt eine rudimetäre Home-Page erstellt. Das Hauptproblem dabei waren Dialoge zum Anmelden und Registrieren. Ich habe viele Web-Seiten studiert und am Ende auch eine gute Lösung gefunden, wobei ich vorerst das Speichern der Daten in einer DB außen vor gelassen habe.
Kürzlich habe ich mir MySQL angeeignet, eine passende DB erstellt und versuche nun, das Anmelden usw. fertig zu stellen. Leider gibt es dabei ein kleines Problem, dass ich nicht lösen kann. Meine Dialoge sind vollständig in JavaScript geschrieben und beötigen ansonsten nur noch css für das Layout. Dies bedeutet, das der jeweilige Dialog ausschließlich beim Client erstellt wird und dort werden auch die Daten erfasst und formal überprüft. Wenn das funktioniert hat, müssen die Daten via PHP zum Server übertragen werden, und dort in der MySQL DB geprüft bzw. gespeichert werden. Für die DB-Aktionen gibt es viele Beispiele, und sobald ich z.B. für das Login die Daten eMail, PW aus dlgLogin.js nach serverLogin.php übertragen habe, sollte der Rest kein Problem mehr sein.
Einige Tage habe ich das mit fetch versucht, leider ohne Erfolg. Heute habe ich im Netz ein Beispiel mit jQuery und Ajax gefunden, das ich bei mir nachbauen konnte und das auch funktioniert hat. Analog habe ich das auf mein Login übertragen, aber da bekomme ich einen Fehler, den ich nicht nachvollziehen kann. Im Vorgängerbeispiel steht auf einer Html-Seite im Kopf ein Verweis auf jQuery
<script src="jQuery.js"></script>
und im Scriptbereich im Kern das Folgende:
<script>
...
$.ajax({
method: "POST",
url: "wrap.php",
data: { text: frmText2 }
}).done(function( response ) {
$("p.broken").html(response);
});
</script>
Dabei ist mir schleierhaft, worauf sich das $-Zeichen vor dem ajax bezieht und beim Versuch, das zu googeln bekomme ich nur Seiten über einen niederländischen Fußballverein oder grundlegende Seiten über JavaScript. Ich habe das nun sinngmäß auf mein Login übertragen und versuche vorerst nur, die Daten zu übertragen und eine entsprechende Textmeldung zurückzubekommen. Textauschnitte aus dlgLogin.js:
// Login-Daten überprüfen
function checkLoginData(aMailAdr, aPassWd) {
let res = false;
let msg = "";
try {
let logResult = doChkLoginData(aMailAdr, aPassWd);
res = logResult.logRes;
msg = logResult.logMsg;
} catch(err) {
res = false;
msg = "Fehler bei der Datenüberprüfung: " + err.message;
}
return {logRes: res, logMsg: msg};
}
// Login-Daten überprüfen: Durchführung
function doChkLoginData(aMailAdr, aPassWd) {
let res = false;
let msg = "";
$.ajax({
method: "POST",
url: "serverLogin.php",
data: {eMail: aMailAdr, passWd: aPassWd}
}).done(function(response) {
msg = response;
});
return {logRes: res, logMsg: msg};
}
sowie die Unit serverLogin.php:
<?php
// Empfangene Daten auslesen
$eMailAdr = $_POST['eMail'];
$passWord = $_POST['passWd'];
// Antwort (vorerst)
$textAnswer = "eMail-Adresse: " + $eMailAdr + ", Passwort: " + $passWord;
echo $textAnswer;
?>
Beim Testen bekomme ich genau das als Fehler, was ich oben auch nicht verstanden habe, nämlich die Meldung "$ is not defined". Der Unterschied zwischen den beiden Ajax-Beispielen ist, dass im ersten Fall im Kopf die Referenz auf jQuery.js eingetragen ist, während in meiner dlgLogin.js Unit ein solcher Verweis nicht möglich ist, sondern im Hauptformular steht, aus dem heraus der Dialog aufgerufen wird.
Ich hoffe, dass mir jemand weiterhelfen kann.
Hallo Aragorn,
jQuery muss man vorher downloaden und zusammen mit den Scripts der eigenen Seite verfügbar machen. Ich nehme an, dass dein script src="jQuery.js" mit einem HTTP 404 fehlgeschlagen ist.
$ ist in JS ein gültiger Variablenname und wird von jQuery mit dem Objekt belegt, das die Zugriffsfassade für jQuery darstellt. Außer $ stellt jQuery sich auch über die Variable jQuery bereit. Aber wenn das Script nicht lädt, dann fehlt die ebenfalls.
jQuery muss man heutzutage allerdings als weitgehend obsolet einstufen. Ein paar Sachen mögen damit bequemer gehen, und es gibt eine Menge Plugins, die man vielleicht nutzen möchte, aber $.ajax lässt sich durch fetch prima ersetzen.
Unsere Beschreibung für POST Requests mit fetch ist im Wiki leider rudimentär, aber wenn Du zeigst, was Du da versucht hast, kriegen wir das bestimmt zusammen hin. jQuery ist in der Beziehung wie Regex - du willst damit ein Problem lösen und hast dafür zwei andere.
Eins ist wichtig bei $.ajax und auch bei fetch: Sie laufen asynchron. Der Teil
$.ajax(...)
.done(function(response) {
msg = response;
});
return {logRes: res, logMsg: msg};
suggeriert, dass man die Response des Servers unter logMsg zurückgeben würde, aber die Callback-Funktion bei done() wird erst nach Rückkehr vom Server aufgerufen, während das return-Statement sofort nach Versand des Requests ausgeführt wird. Abgesehen davon setzt Du res nie auf true.
Ich muss jetzt weg, ich schreibe in ein paar Stunden noch was dazu.
Rolf
Hallo Rolf,
vielen Dank für die Antwort, das war sehr hilfreich. Im einzelnen:
jQuery hatte ich heruntergeladen und im Test-Projekt eingefügt, aber leider hatte ich in der Zeile <script src="jQuery.js"></script> beim Kopieren eine kleine Korrektur vergessen. Außerdem hatte ich nach einer längeren Web-Entwicklungspause vergessen, wie im Firefox-Browser die Entwickler-Werkzeuge funktionieren. Nach einer Korrektur ist der $-Fehler jetzt verschwunden.
Das res nie auf true gesetzt wird, ist klar, da fehlt in der Server-Unit noch der ganze Abschnitt mit MySQL, wo die Daten geprüft werden. Ich wollte mit dieser Konstruktion erst einmal sicher stellen, dass die Daten korrekt beim Server ankommen.
Die Aufruf-Funktion im Login-Dialog habe ich wie folgt korrigiert:
// Login-Daten überprüfen
function checkLoginData(aMailAdr, aPassWd) {
let res = false;
let msg = "";
try {
let logResult = doChkLoginData(aMailAdr, aPassWd);
if (logResult) {
res = logResult.logRes;
msg = logResult.logMsg;
} else {
msg = "Datenüberprüfung fehlgeschlagen.";
}
} catch(err) {
res = false;
msg = "Fehler bei der Datenüberprüfung: " + err.message;
}
return {logRes: res, logMsg: msg};
}
// Login-Daten überprüfen: Durchführung
function doChkLoginData(aMailAdr, aPassWd) {
let res = false;
let msg = "";
$.ajax({
method: "POST",
url: "serverLogin.php",
data: {eMail: aMailAdr, passWd: aPassWd}
}).done(function(response) {
msg = response;
return {logRes: res, logMsg: msg};
});
}
Leider funktioniert das auch noch nicht korrekt. Ich hätte gerne, dass die (banale) Servermeldung in dem Dialog ankommt und wiedergegeben wird. Dann könnte ich die Server-Unit zuende programmieren. Tatsächlich meldet er aber "Datenüberprüfung fehlgeschlagen".
Offensichtlich funktioniert "doChkLoginData" nicht so, wie ich es gerne hätte. Er sollte dort solange warten, bis er Antwort vom Server bekommt oder eine gewisse Zeit verstrichen ist. Ich weiß nur nicht, wie ich das programmieren soll.
Hallo Aragorn,
Warten gehört zu den Dingen, die ein JavaScript-Programm im Browser nicht tun darf. Das ist wie in der Windows-Messageloop, da darf man auch nicht warten.
In node.js sieht das anders aus, da gibt es wartende Funktionen, sie können Dich dort aber auch gründlich abschießen.
JavaScript im Browser ist konstruktiv am Warten gehindert. Alles weitere steht in meinem Roman über Promises 😉
Rolf
Hallo Aragorn,
grundsätzlich funktioniert fetch() auf Basis von Promises. Wir haben dazu im Wiki ein Tutorial, das sich schon länger im Zustand der Überarbeitung befindet.
Der eigentliche Ablauf von fetch() geschieht in drei Phasen.
Die Übergänge von 1 nach 2 und von 2 nach 3 werden mit Promises realisiert. Promises gab es in jQuery schon bevor sie als Javascript-Kernfeature eingeführt wurden, sie heißen dort allerdings Deferred Objects und haben ein etwas anderes API. Die .done()-Methode in jQuery heißt bei Promises .then(), und JavaScript bietet mit async/await ein Pfund Syntaxzucker, um Promises besser handhaben zu können.
Der Aufruf lautet fetch(url, options). Standardmäßig, d.h. ohne Options, macht fetch einen GET-Request. Du möchtest einen POST und dafür brauchst Du mindestens 2 Einträge im options-Objekt (das ist ein einfaches, flaches Objekt):
function checkLoginData(mailAddress, password) {
const loginBody = new FormData();
loginBody.set("eMail", mailAddress);
loginBody.set("passWd", password);
fetch("serverLogin.php", { method: "POST", body: loginBody });
}
Es gibt diverse Möglichkeiten, die Daten für Fetch zu übertragen. Ein FormData-Objekt kommt dem data-Objekt, das Du bei $.ajax gesehen hast, am nächsten.
Was man alles als Optionen übergeben kann, ist bei Mozilla als RequestInit-Objekt beschrieben. Vorsicht, Gefahr des mentalen Durchbrennens, da steht eine MENGE technisches Zeug. Für FormData-Übertragungen an den eigenen Server genügt meistens method und body, ggf. kannst Du noch HTTP Request-Header hinzufügen. Aber die technische Komplexität steckt im AJAX-Prinzip, jQuery oder (ganz klassisch) XMLHttpRequest bewahren Dich auch nicht davor.
fetch liefert ein Promise zurück, auf das Du nun warten musst. Insbesondere muss der checkLoginData-Aufrufer darauf warten, bevor er die Antwort auswertet. "Warten" ist etwas, das JavaScript überhaupt nicht mag, deswegen verwendet man Callback-Funktionen, die man in Promises kapselt, oder die async/await Syntax, die unter der Haube das Gleiche tut.
Das erste Promise erfüllt sich (d.h. der erste then-Callback wird aufgerufen), sobald die Antwort des POST-Requests eingetroffen ist und die HTTP Header der Antwort bereitstehen. Der Datenteil hängt jetzt noch auf der Leitung! Der then-Callback bekommt ein Response-Objekt übergeben und kann es auswerten.
fetch("serverLogin.php", { method: "POST", body: loginBody })
.then(function(response) {
if (response.ok) // Statuscode 200-299
return response.text();
else
throw new Error(`Login-Fehler! Status: ${response.status} ${response.statusText}`)
})
Dieser erste .then()-Callback testet den Erfolg des Requests und legt fest, wie die Antwortdaten (der Inhalt der POST-Antwort) zu verarbeiten ist. In deinem Fall ist es plain text (du machst einen einfachen echo), für die Zukunft solltest Du einen JSON-String zurückgeben und im PHP application/json als Content-Type setzen. Auf JavaScript-Seite rufst Du dann response.json() statt response.text() auf, dann bekommst Du direkt das geparste Objekt.
Im Fehlerfall wirft das Beispiel ein Error-Objekt. Dieser String in Backticks ist ein Template-Literal, falls Du das noch nicht kennst.
Die then-Methode des fetch-Promise gibt Dir ein neues Promise zurück. Wie sich dieses erfüllt, hängt davon ab, was der then-Callback tut.
Der Aufruf von response.text() liefert seinerseits ein Promise. Dieses wird an then() zurückgegeben, und then() koppelt sich daran. D.h. sobald der Browser den Antworttext eingelesen hat, wird das text()-Promise erfüllt und damit auch das then()-Promise. Um darauf reagieren zu können, rufen wir auf dem then()-Promise noch einmal then() auf. Diesmal bekommen wir den Wert übergeben, den der vorige then-Callback zurückgegeben hat. Weil das ein Promise war, bekommen wir denjenigen Wert, den response.text() als Ergebnis im Promise hinterlegt hat.
fetch("serverLogin.php", { method: "POST", body: loginBody })
.then(function(response) {
if (response.ok) // Statuscode 200-299
return response.text();
else
throw new Error(`Login-Fehler! Status: ${response.status} ${response.statusText}`)
})
.then(function(text) {
console.log(text);
});
Die Frage ist nun, was man damit tun will. Du möchtest ja eigentlich dem Aufrufer mitteilen, ob der Login erfolgreich war oder nicht. Das eigentliche serverLogin.php sollte also schauen, ob das Passwort zur Mailadresse passt (siehe unser PHP Tutorial zu Konzepten eines Login-Systems und dann nur true oder false zurückgeben. Oder 1 und 0. Mehr nicht, man darf aus der Antwort nicht schließen können, ob die Mailadresse oder das Passwort falsch ist. Im Falle einer unbekannten Mailadresse solltest Du password_verify() trotzdem aufrufen, mit Dummydaten, damit die Requestlaufzeit gleich ist. Andernfalls könnte jemand per Laufzeitstatistik auf den Unterschied "falsche Mail" oder "falsches Passwort" zu schließen versuchen.
Du solltest also im zweiten then() die Antwort des Servers anschauen und daraus einen boolschen Wert machen, und dann das Ergebnis dieses Aufrufbandwurms an den Aufrufer zurückgeben.
function checkLoginData(mailAddress, password) {
const loginBody = new FormData();
loginBody.set("eMail", mailAddress);
loginBody.set("passWd", password);
return fetch("serverLogin.php", { method: "POST", body: loginBody })
.then(function(response) {
if (response.ok) // Statuscode 200-299
return response.text();
else
throw new Error(`Login-Fehler! Status: ${response.status} ${response.statusText}`)
})
.then(function(text) {
return (text == 'ok');
});
}
...
checkLoginData(mail, password)
.then(function(success) {
if (success) {
// was auch immer du bei erfolgreichem Login tun willst
}
else {
// was auch immer du bei nicht erfolgreichem Login tun willst
}
})
.catch(error) {
// Hier kommst Du hin, wenn etwas schiefging
}
D.h. deine checkLoginData()-Funktion gibt jetzt ihrerseits ein Promise zurück, und der Aufrufer kann mit .then() auf dieses Promise warten. Der then-Callback des Aufrufers bekommt deinen Rückgabewert. Das muss man so machen, weil checkLoginData() mit diesem Promise zurückkehrt, sobald der fetch-Request weggeschickt wurde. Das ist bei jQuery übrigens genauso, auch dort musst du den asynchronen Ablauf beachten und auf Deferred-Objekte warten.
Sobald fetch oder einer der beiden then-Callbacks mit throw abbrechen, wird das zugehörige Promise abgelehnt und weitere, durch then-Aufrufe angehängte Folgepromises, ebenfalls. Auf abgelehnte Promises reagiert man mit .catch() (in jQuery mit .fail() oder .catch()) und bekommt das geworfene Objekt als Argument. Was auch immer Du dann damit tust.
Das ganze lässt sich visuell von Callbacks befreien, wenn man den async/await Syntaxzucker einsetzt. Technisch befinden sich immer noch Callbacks und Promises unter der Haube, aber JavaScript baut das automatisch für dich.
async function checkLoginData(mailAddress, password) {
const loginBody = new FormData();
loginBody.set("eMail", mailAddress);
loginBody.set("passWd", password);
const response = await fetch("serverLogin.php", { method: "POST", body: loginBody });
if (response.ok) { // Statuscode 200-299
const antwort = await response.text();
return (antwort == 'ok');
// oder als Einzeiler:
return (await response.text() == 'ok');
}
else {
throw new Error(`Login-Fehler! Status: ${response.status} ${response.statusText}`);
}
)
...
// Der folgende Code muss seinerseits in einer async-Funktion stehen oder
// Top-Level Code eines ECMAScript-Moduls sein (script type="module")!
try {
if (await checkLoginData(mail, passwort)) {
// was auch immer du bei erfolgreichem Login tun willst
}
else {
// was auch immer du bei nicht erfolgreichem Login tun willst
}
}
catch(error) {
// Hier kommst Du hin, wenn etwas schiefging
}
async-Funktionen geben nicht den Wert zurück, den Du bei return angibst, sondern ein Promise, das sich erfüllt, sobald die async-Funktion zum return kommt. Das Promise wird in dem Moment zurückgegeben, wenn die async-Funktion ihren ersten eigenen await macht (siehe meine Promise-Tutorial Überarbeitung).
Wird das Promise erfüllt (d.h. die Funktion, die das Promise lieferte, war erfolgreich), bekommst Du den Erfüllungswert von await zurück. Wird es zurückgewiesen (entweder durch explizites Zurückweisen im Promise-API oder durch Werfen einer Exception), dann wirft await seinerseits eine Exception und deshalb muss da ein try/catch drumherum.
Ich hoffe, das war jetzt nicht zu unverständlich.
Rolf
@@Rolf B
fetch("serverLogin.php", { method: "POST", body: loginBody }); .then(function(response) {
Da ist ein ; zu viel, nicht wahr? (Das zieht sich durch mehrere Beispiele.)
🖖 Live long and prosper
Hallo Gunnar,
ups, Kopierfehler.
Danke, ich habe es repariert
Rolf
Hallo Rolf,
vielen Dank für diese ausführliche Anwort. Ich werde versuchen, mich da durchzukämpfen. Das wird etwas dauern.