PHPMailer - Mehrere While-Schleifen hintereinander

Überkinger

Lieutenant
Registriert
Juli 2010
Beiträge
600
Hallo,

ich setze den PHPMailer ein, um meine Kunden täglich mit zwei Newslettern zu versorgen (Kunden wissen, dass sie zweimal am Tag Newsletter erhalten). Sie kaufen entsprechende Abos. Es geht hier um den Versand an ca. 1000-2000 Kunden.

Ich verschachtele den Versand, je nach Kundenattribute via while schleife.


PHP:
// Versand an Kunde A - HTML
if ($a==1) {

while ($row_a = mysql_fetch_array($result_a)) {
$username  = $row_a["forename"] . ' ' . $row_a["nickname"] . ' ' . '['.$row_a["number"].']';
$mail->AddAddress($row_a["email"], "$username");
 	if(!$mail->Send()) {
	echo "Mailer Error (" . str_replace("@", "@", $row_a["email"]) . ') ' . $mail->ErrorInfo . '<br>';
	} else {
	echo "Message sent to :" . $username . ' (' . str_replace("@", "@", $row_a["email"]) . ')<br>';
	}
   $mail->ClearAddresses();
   $mail->ClearAttachments();
 }
}

// Versand an Kunde B - PDF

if ($b==1) {
while ($row_b = mysql_fetch_array($result_b)) {
$username  = $row_b["forename"] . ' ' . $row_b["nickname"] . ' ' . '['.$row_b["number"].']';
$mail->AddAddress($row_b["email"], "$username");
 	if(!$mail->Send()) {
	echo "Mailer Error (" . str_replace("@", "@", $row_b["email"]) . ') ' . $mail->ErrorInfo . '<br>';
	} else {
	echo "Message sent to :" . $username . ' (' . str_replace("@", "@", $row_b["email"]) . ')<br>';
	}
   $mail->ClearAddresses();
   $mail->ClearAttachments();
 }
}

Frage: Jede while-Schleife wird doch erst komplett durchlaufen, bevor die nächste Schleife startet?

Anfangs wurde die whileschleife so bei jedem Kunden gestartet:
PHP:
while ($row = mysql_fetch_array($result_a)) {
}

while ($row = mysql_fetch_array($result_b)) {
}

Der nächste Versuch wird mit obigen Beispiel
PHP:
($row_a / $row_b)
gestartet. Ich vermute, dass dass Array gar nicht geelert worden ist und es in der zweiten while-schleife der inhalt nochmals zum Versand herangezogen wurde.
 
Zuletzt bearbeitet:
kann dir da grad net ganz folgen.

was soll denn
Code:
($row_a / $row_b)
machen?

und wo genau liegt nun dein problem?

Und ja, eine while schleife wird erst komplett durchgelaufen (inkl. aller iterationen) ehe die nächste startet, oder aber sie läuft keinmal weil direkt zu beginn die abbruch bedinung erfüllt ist.
 
...oder es gibt irgendwo ein break; oder ein continue;

Ich würde aber gar nicht auf eine selbst geschriebene Lösung setzen. Ich würde eher ein freies Newsletter-System verwenden. Da hast du normalerweise deutlich bessere Optionen hinsichtlich des Versands. Richtig gute Systeme senden die Mails z.B. nicht stur innerhalb eines Durchlaufs, sondern verzögern den Versand. Nicht jeder Mailserver spielt mit, wenn man innerhalb einer Minute >100 Mails verschicken will.
 
oder ein goto, exit oder die(), ein error oder eine exception -_-
ernsthaft ? :-/
 
break und continue sind grundlegende Elemente von Kontrollstrukturen. Nutze ich regelmäßig in Schleifen. Das kann man von goto, die und exit nicht unbedingt behaupten.
 
schleifen mit break? dann würde ich da evlt. nochmal den algorithmus prüfen! Zeugt von sehr unsauberen Style. Hat nur was in nem switch verloren - zumindest in 99% der fälle, ganz selten kann es wirklich mal nötig sein ne schleife vorzeitig zu beenden, ohne das es im schleifen kopf steht.

außerdem ging es ja nur um möglichkeiten wie man nun die while schleife killt und btw.: auch mit 100 continue's wird die schleife vollständig durchlaufen!
 
Daaron schrieb:
...oder es gibt irgendwo ein break; oder ein continue;

Ich würde aber gar nicht auf eine selbst geschriebene Lösung setzen. Ich würde eher ein freies Newsletter-System verwenden. Da hast du normalerweise deutlich bessere Optionen hinsichtlich des Versands. Richtig gute Systeme senden die Mails z.B. nicht stur innerhalb eines Durchlaufs, sondern verzögern den Versand. Nicht jeder Mailserver spielt mit, wenn man innerhalb einer Minute >100 Mails verschicken will.

Meine eigene Lösung funktioniert automatisch. Sobal eine bestimmte Datei auf dem Server eingeht, startet das Versandscript, stellt Inhalte zusammen und PHP-Mailer soll es versenden. Betreibe einen eigenen Server, der ca. 750 Mails / Minute schafft. Ist also alles kein Problem. Ich bediene etwa 1000-2000 Kunden damit.

kann dir da grad net ganz folgen.

was soll denn
Code:

($row_a / $row_b)

machen?

und wo genau liegt nun dein problem?

Das sorgt dafür, dass jede Schleife mit einem eindeutigen array bedient wird. Grundproblem. Die Kunden erhalten doppelte, dreifache oder gar manchmal vierfach die selbe Mail zugestellt. Ich vermute, dass das

a) an nicht eindeutigen Arrays liegt
b) die versandten Adressen nicht schnell genug gelöscht werden
c) und wegen punkt b) $row bei Kunde A und B verwendet wird, es womöglich deswegen zum Mehrfachversand kommt.
 
Zuletzt bearbeitet:
hmm folgende fragen /anregungen habe ich dazu:

sind die adressen vlt. mehrfach in der DB?
das die variablen doppelt belegt sind kann eigentlich nicht sein, ebenso wie die adressen auch wieder gelöscht werden.
werden denn alle e-mails verschickt oder kommt es auch mal zu einem error?
Denn bei dem error muss ich fragen ob
Code:
$mail->ErrorInfo
so richtig ist und nicht ehr
Code:
$mail->ErrorInfo()
heißen müsste.
dann könnte es sein das die clearAdresses() methode net aufgerufen wird.

aber ohnehin eine weitere frage:
ich kenne dein tool zu versenden nun nicht, es wirkt aber so als sollte man erst alle adressen hinzufügen und dann senden lassen.
Die methoden namen nutzen ja alle den plural.
Du machst es aber so:
adresse einfügen, senden, adresse entfernen.

bei 20 mails könntest dir also ggf. 19 mal das entfernen sparen.

P.S.
bei genauerer und kleinlicherbetrachtung kann auch eine durch break; beendet schleife als durchgelaufen betrachtet werden, sie ist ja beendet und die zweite wird in jedemfall niemals vor der ersten, oder währenddessen laufen, es sei denn man zwingt mit irgendwelchen kontrollstruckturen dazu. Auch wenn sie dann evtl. nicht vollständig durchlaufen ist
 
Zuletzt bearbeitet:
Mercsen schrieb:
hmm folgende fragen /anregungen habe ich dazu:

sind die adressen vlt. mehrfach in der DB?

Kann nicht sein, da ich die Kunden selektiere nach kundenspezifischen Datenfeldern. Ich habe die Ausgabe des Datenbankquerys auch via phpmyadmin überprüft. Doppelte Ausgaben kamen nicht vor.

das die variablen doppelt belegt sind kann eigentlich nicht sein, ebenso wie die adressen auch wieder gelöscht werden.
werden denn alle e-mails verschickt oder kommt es auch mal zu einem error?

Ok, also keine Überbleibsel. Auf Errors kann ich erst heute Abend prüfen, beim nächsten Versand. Diesen starte ich dann manuell von der Shell und schreibe den output aller Zieladressen in ein File.

Denn bei dem error muss ich fragen ob
Code:
$mail->ErrorInfo
so richtig ist und nicht ehr
Code:
$mail->ErrorInfo()
heißen müsste. Dann könnte es sein das die clearAdresses() methode net aufgerufen wird.

Der Code der Schleife stammt direkt von den Programmierend des PHPMailers. Daher sollte das schon passen.
http://code.google.com/a/apache-extras.org/p/phpmailer/wiki/SmtpDatabase

aber ohnehin eine weitere frage:
ich kenne dein tool zu versenden nun nicht, es wirkt aber so als sollte man erst alle adressen hinzufügen und dann senden lassen.
Die methoden namen nutzen ja alle den plural.
Du machst es aber so:
adresse einfügen, senden, adresse entfernen.

bei 20 mails könntest dir also ggf. 19 mal das entfernen sparen.

Das mache ich deswegen, damit ich kundenbezogene Mails verschicken kann. So steht als Zieladresse nicht nur seine eMail-Adresse, sondern auch sein Vor- und Zunahme, sowie seine Kundennummer. Damit kann man sehr viel dazu beitragen, dass die eigenen Mails bei den Kunden nicht als Spam eingestuft und womöglich wegsortiert werden. Würde ich alle Adresse auf einmal ansprechen, oder via BCC senden, was ja auch gienge, wäre kein kundenspezifischer Versand mehr möglich.
 
Zuletzt bearbeitet:
also im code selber sehe ich nun keine ursache dafür das die user mails doppelt und dreifach bekommen, da hilft nur klassisches debuggen:

starte doch einfach mal einen testlauf, aber kommentiere den teil wo er sendet aus und lass dir nur ausgeben an wen er, theoretisch, eine mail geschickt hätte.

wenn dann eine saubere liste, mit nur einem empfänger pro zeile ohne wiederholungen hast, liegst an der send() methode o.ä.
 
Mercsen schrieb:
wenn dann eine saubere liste, mit nur einem empfänger pro zeile ohne wiederholungen hast, liegst an der send() methode o.ä.

Gesend wird direkt an den Mailserver via SMTP-Protokoll.

Ich habe den Test jetzt durch. Alle Datenbankabragen kommen nur einmal vor. Dazu habe ich jede Schleife mit einem Zähler versehen und lasse mir die Liste der eMail-Adressen ausgeben. Das sieht alles sauber aus. Es sind keine doppelten Einträge vorhanden und die Adressen werden sauber aus dem Array entfernt.

Ich bin ratlos. Jetzt kann es nur noch am Mailserver liegen, oder?
Ergänzung ()

Ich glaube ich habe den Fehler gefunden.

Das Versandscript wird vom Crontab jede Minute aufgerufen und ausgeführt. Das bedeutet, dass die Schleifen pro Scriptaufruf mehrfach durchlaufen werden, eben solange, bis die Versandschleifen an den SMTP-Server durchlaufen werden. Da der Versand etwa 3-4 Minuten dauert, wurde das Versandscript auch drei bis vier Mal gestartet und die Versandschleifen erneut gestartet.

Wie kann ich verhindern, dass das Versandscript überhaupt ausgeführt wird, wenn es schon einmal gestartet wurde?
 
Zuletzt bearbeitet:
Mach' die doch ein Flag in die DB oder eine Datei mit dem Datum, wann das Script gestartet ist und lösche es wieder wenn der Versand fertig ist.
Wenn das Datum ein paar Stunden alt ist, weißt du auch, dass bei der vorherigen Ausführung etwas schief gegangen ist.

Alternativ: Ein CronJob nur für den E-Mail-Versand erstellen.
 
Darlis schrieb:
Mach' die doch ein Flag in die DB oder eine Datei mit dem Datum, wann das Script gestartet ist und lösche es wieder wenn der Versand fertig ist.
Wenn das Datum ein paar Stunden alt ist, weißt du auch, dass bei der vorherigen Ausführung etwas schief gegangen ist.

Alternativ: Ein CronJob nur für den E-Mail-Versand erstellen.

Hi,

oder ich setze ans Ende jeder Schleife ne Variable.

PHP:
if ($sended_a == 0 AND !sended_b == 0) {
// Keine Versandschleife aktiv, Versand starten


    // Versand an Kunde A - HTML
    
    if ($a==1) {
     
    while ($row_a = mysql_fetch_array($result_a)) {
    
    $username = $row_a["forename"] . ' ' . $row_a["nickname"] . ' ' . '['.$row_a["number"].']';
    $mail->AddAddress($row_a["email"], "$username");
    if(!$mail->Send()) {
    echo "Mailer Error (" . str_replace("@", "@", $row_a["email"]) . ') ' . $mail->ErrorInfo . '<br>';
    } else {
    echo "Message sent to :" . $username . ' (' . str_replace("@", "@", $row_a["email"]) . ')<br>';
    }
    $mail->ClearAddresses();
    $mail->ClearAttachments();
    
    }
$sended_a = 1;
    }
     
    // Versand an Kunde B - PDF
     
    if ($b==1) {
    while ($row_b = mysql_fetch_array($result_b)) {
    $username = $row_b["forename"] . ' ' . $row_b["nickname"] . ' ' . '['.$row_b["number"].']';
    $mail->AddAddress($row_b["email"], "$username");
    if(!$mail->Send()) {
    echo "Mailer Error (" . str_replace("@", "@", $row_b["email"]) . ') ' . $mail->ErrorInfo . '<br>';
    } else {
    echo "Message sent to :" . $username . ' (' . str_replace("@", "@", $row_b["email"]) . ')<br>';
    }
    $mail->ClearAddresses();
    $mail->ClearAttachments();
    }
$sended_b = 1;
}
}
else {
echo "Versand läuft.... Keine Abarbeitung.
}
 
die variable muss aber denoch in der DB gespeichert werden, wenn dein cronJob dann nämlich das Script nochmal startet, dann ist der flag wieder weg (php bsitzt an sich keinen peristenten speicher, höchsten über eine session).
Jeder Aufruf von send.php (oder wie auch immer) belegt den Speicher mit neuen Variablen.

D.h. 2 aufrufe erzeugen auch 2 mal die variable $sended_a, jede mit einem eignen wert und vorallem einer eigenen Adresse!

Am besten machst du es wie Darlis sagte, du speicherst zu jeder versendeten e-mail adresse das die mail raus ist und in dem query mit dem du die adressen ausliest musst du eben nach nicht geendeten mails filtern.

bei solch gewaltigen mengen an mails frage ich mich aber ob php dafür die richtige Sprache ist, sollte was schief gehen siehst das eben immer erst ganz am ende, da wäre dann eine verknüpfung mit Techniken wie ajax sinnvoll um die ganze Kontrolle zu haben.
Alernativ ne andere Sprache nehem die nicht einfach nur runtergerattert wird.

Wieso wird das Script eigentlich im Minutentakt aufgerufen, wenn du oben sagst die User bekommen 2 mal am Tag eine e-mail?
Oder verstehe ich etwas nicht?
 
Mercsen schrieb:
Wieso wird das Script eigentlich im Minutentakt aufgerufen, wenn du oben sagst die User bekommen 2 mal am Tag eine e-mail?
Oder verstehe ich etwas nicht?

Das liegt daran, das die Ausgabezeiten im Extremfall varieren können. Wenn ich von unterwegs aus arbeite, kann es passieren dass die zu versendeten Informationen viele Stunden später als im Regelbetrieb abgeschickt werden, aber wenn sie an den Server ausgeliefert sind, müssen sie eben schnell raus.

Andere Techniken...gut, aber da kenne ich mich ja mal gar nicht aus. Ich glaube, das Arbeiten mit einer Session ist eine gute Idee, da ich auf meinem Server die Sessiondauer frei einstellen kann. Ne Idee, wie ich das mit der Session am besten mache?
 
so leicht wird das nicht, wenn du session übergeben willst an einen anderen clienten besteht die gefahr das auch ein anderer das macht, am sinnvollsten wäre es einfach schnell nen flag in der DB oder einer datei zu speichern.

In der schleife machst dann sowas wie folgendes:

Code:
while ($row_a = mysql_fetch_array($result_a)) {
$username  = $row_a["forename"] . ' ' . $row_a["nickname"] . ' ' . '['.$row_a["number"].']';
$mail->AddAddress($row_a["email"], "$username");
 	if(!$mail->Send()) {
            	echo "Mailer Error (" . str_replace("@", "@", $row_a["email"]) . ') ' . $mail->ErrorInfo . '<br>';
	} else {
	echo "Message sent to :" . $username . ' (' . str_replace("@", "@", $row_a["email"]) . ')<br>';
 mysql_query('UPDATE user_table SET sended = true, date = ' . time() . ' WHERE nickname = "' $row_a['nickname'] . '"');

	}
   $mail->ClearAddresses();
   $mail->ClearAttachments();
 }
}

jetzt kannst du im query, der die user lädt, als bedinung setzen das sended = false sein muss, oder date < als der letze update zyklus, what ever.

So ballerst natürlich die DB mit querys zu, richtiger wäre es alle ID's zu speichern die erfolgreich gesndet wurden und abschlieénd einen großen Query an die DB zu schicken, allerdings kann dann wieder das problem auftreten das ein script startet bevor das eine abgelaufen ist.

Wieso erhöhst nicht einfach von 1 auf z.b. 10 min? das dürfte ja noch vertretbar sein.

Ansonsten würde ich sicherstellen das dass Script nur einmal läuft, z.b. mit der text datei in der nur 1 oder 0 steht.
Am anfang wird die datei geöffnetm steht 1 drinne, heißt es das script wird noch ausgeführt, also wird nix gemacht.

Steht 0 drinne kann er loslegen und setzt direkt eine 1 da rein.

sonst musst du eine session erzeugen, diese dann dem server zuweisen, welcher das senden anstößt und dann könntest anschließend auf die session daten zgreifen, aber das ist nicht der optimale weg.
 
Zuletzt bearbeitet:
Mercsen schrieb:
Ansonsten würde ich sicherstellen das dass Script nur einmal läuft, z.b. mit der text datei in der nur 1 oder 0 steht. Am anfang wird die datei geöffnetm steht 1 drinne, heißt es das script wird noch ausgeführt, also wird nix gemacht.

Ich überprüfe ohnehin auf Existenz von Dateien, bevor das Script startet. Dann prüfe ich einfach zusätzlich auf ne vorhandene Datei, mit der ich Anfang und Ende der Schleifen markiere. Vor der Schleife erstelle ich die Datei, nach der letzten Schleife lösche ich es wieder.

Aber ne Sessionvariable $_Session[sendfile_locked] würde doch auch reichen. 1 vor der ersten Schleife, 0 nach der letzten Schleife.
 
natürlich ich hab das auch ein wenig falsch verstanden gehabt.....
aber ist ja immerder selbe server der das senden anstößt, von daher sollte es klappen. gibts denn fortschritte?
 
Mercsen schrieb:
natürlich ich hab das auch ein wenig falsch verstanden gehabt.....
aber ist ja immerder selbe server der das senden anstößt, von daher sollte es klappen. gibts denn fortschritte?

Ja, allerdings funktionieren die Sessions von der Shell aus nicht, von der Webseite schon. session_start(); ist in 2. Zeile des Scripts, aber wenn ich die Session in einem anderen PHP-File abfrage, ist die Sessin nicht da, auch in der selben Datei nicht wenn ich sie ein zweites mal ausführe.

Ich habe jetzt einfach die Lösung mit dem lockfile genommen, das funktioniert soweit, fast. :-)

Allerdings klappt das löschen nicht.

PHP:
$lockfile = '/scripts/lockfile';
unlink ($lockfile);

Das Lockfile wird einfach nicht gelöscht. Soweit ich gesehen habe, gab es auch keine Fehlermeldung. Nehm ich obigen Zweizeiler und führe ihn später aus, wird das File gelöscht.
 
also session_start() musst du auf jeder seite ausführen auf der du mit $_SESSION arbeiten willst.
ist schon eine session vorhanden startet session_start() keine neue, sondern stellt die alte wieder her ;)

wozu das file löschen?
den wert einfach auf 0 zu ändern, also das dass script grad net läuft, reicht ja.
 
Zurück
Oben