Moin Moin!
Dazu nutz eich folgende (nicht von mir stammende) Funktion:
// Is the number valid against luhn?
> >
> > \*SCHAUDER\*
>
> Wieso schauderts dich da?
Weil gerade einmal drei Zeilen sauber sind: Eine Initialisierung, die Verdoppelung, und das Aufsummieren der Ziffern bzw. Quersummen.
> ~~~php
> // Is the number valid against luhn?
> $cardNumber = strrev($this->__ccNum);
Eingabe umkehren und kopieren, egal wie lang sie ist. Ich unterstelle bei PHP mal, dass wir über ein Webserver-Environment reden. Die Eingabe kommt also sehr wahrscheinlich von einem nicht kontrollierbarem Programm und kann auch mal ein paar Megabyte groß sein. Diese Operation verdoppelt mal eben den Speicherbedarf.
Die Umkehrung ist noch nicht einmal notwendig, der PHP-Code in der Wikipedia zeigt schön, dass es ohne geht.
Wie lang ist eine Kartennummer? Ist eine ein Zeichen lange Kartennummer gültig? Oder eine 200 Zeichen lange Nummer?
Warum fängt die Routine nicht mit so einer Prüfung an? return false if (strlen($cardNumber)<$minLen) or (strlen($cardNumber)>$maxlen;
Danach könnte man darüber nachdenken, Daten umzukopieren und Schleifen laufen zu lassen.
> $numSum = 0;
Kein Einwand.
> for($i = 0; $i < strlen($cardNumber); $i++)
> {
> $currentNum = substr($cardNumber, $i, 1);
Können "Ä", "$" oder "ß" Bestandteil einer gültigen Nummer sein? Nein? Warum steht hier dann nicht ein sofortiger Abbruch, wenn $currentNum keine Ziffer ist?
return false unless $currentNum>="0" and $currentNum<="9";
(Funktioniert so nur mit ASCII und kompatiblen Zeichensätzen, bei denen "0" bis "9" im Zeichensatz direkt aufeinander folgen)
Alternativ könnte der Test vor der for-Schleife in einer RegExp laufen, die könnte auch gleich die Länge prüfen:
return false unless $cardNumber=~/^\d{8,12}$/;
8 wäre die Untergrenze für die Länge, 12 die Obergrenze.
Wenn nur 8 oder 12 erlaubt sind:
return false unless $cardNumber=~/^\d{8}(\d{4})?$/;
(Lies: Acht Ziffern, optional gefolgt von vier weiteren Ziffern.)
> if(floor($currentNum / 2) != $currentNum / 2)
Ein ein Zeichen langer String mit einer Ziffer wird zweimal in eine Zahl umgewandelt, zweimal mit einer Nicht-Integer-Division durch zwei geteilt, einmal gerundet, nur um dann herauszufinden, ob die Zahl gerade oder ungerade ist. Wie kompliziert kann man sich um eine Integer-Modulo-Division herumwerkeln?
if ($currentNum%2 > 0)
ASCII vorausgesetzt würde sogar eine Bit-Operation direkt auf dem Zeichen ausreichen.
> {
> $currentNum *= 2;
Der ein Zeichen lange String mit der Ziffer wird noch einmal in eine Zahl umgewandelt und verdoppelt, soweit ok.
> }
> if(strlen($currentNum) == 2)
$currentNum enthält eine gerade Zahl zwischen 0 und 18.
Wikipedia sagt: "Für jede Ziffer aus welcher 10 oder mehr wird, bilde die Quersumme (heißt: addiere die einzelnen Ziffern)."
Es ist für den Urheber also völlig naheliegend, die Zahl wieder in einen String umzuwandeln und dessen Länge mit 2 zu vergleichen. Mit der Zahl 10 zu vergleichen ist viel zu offensichtlich.
Davon abgesehen KANN $currentNum dann und nur dann größer oder gleich 10 sein, wenn $currentNum vorher verdoppelt wurde. Diese Fallunterscheidung kann also in den vorgerigen if-Block hineingezogen werden, das erspart in der Hälfte aller Fälle den zweiten Vergleich. Notwendig ist es nicht, aber umständlicher.
if ($currentNum>=10)
> {
$currentNum ist hier immer eine Zahl, durch die Multiplikation mit 2 im vorherigen Schritt.
> $firstNum = substr($currentNum, 0, 1);
Wir wandeln sie in einen String, nehmen dessen erstes Zeichen, und packen es in eine neue Variable.
> $secondNum = substr($currentNum, 1, 1);
Wir wandeln sie noch einmal in einen String, nehmen dessen zweites Zeichen, und packen es in eine weitere Variable.
> $currentNum = $firstNum + $secondNum;
Dann wandeln wir die beiden Strings, wandeln sie jeweils in Zahlen um, und addieren sie.
Integer-Division und Modulo-Operator bei einem auf der Modulo-Operation basierenden Prüfung zu verwenden ist dem Autoren offensichtlich zu trivial.
Es geht ohne String-Umwandlungen und Hilfsvariablen.
$currentNum=int($currentNum/10)+($currentNum%10);
> }
> $numSum += $currentNum;
Nichts auszusetzen.
> }
>
> // If the total has no remained its OK
> $passCheck = ($numSum % 10 == 0 ? true : false);
Ein Modulo-Operator! Der einzige im ganzen Code, der bekanntlich eine Prüfsumme auf Modulo-Basis berechnet!
Was liefert der Vergleichsoperator? Vielleicht einen boolschen Wert? Man weiß es nicht so genau, sicherheitshalber machen wir basierend auf dem boolschen Wert des Vergleichs noch einen test, um dann ganz sicher boolsche Werte zu haben. Ich will nicht nachvollziehen, was im Kopf des Urhebers herumspukte, als er diese Zeilen schrieb.
return ($numSum % 10)==0;
>
(Randbemerkung: Meine Code-Fetzen sind in Perl-Syntax. Macht der Gewohnheit.)
Alexander
--
Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".
Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so".