Fehlerhafter PHP-Code: Die 10 häufigsten Fehler, die PHP-Entwickler machen

Veröffentlicht: 2022-03-11

PHP macht es relativ einfach, ein webbasiertes System zu erstellen, was einer der Gründe für seine Popularität ist. Aber ungeachtet seiner Benutzerfreundlichkeit hat sich PHP zu einer ziemlich ausgeklügelten Sprache mit vielen Frameworks, Nuancen und Feinheiten entwickelt, die Entwickler beißen können, was zu stundenlangem haarsträubenden Debugging führt. Dieser Artikel hebt zehn der häufigsten Fehler hervor, vor denen sich PHP-Entwickler hüten müssen.

Häufiger Fehler Nr. 1: Nach foreach -Schleifen hängende Array-Referenzen hinterlassen

Sie sind sich nicht sicher, wie Sie foreach-Schleifen in PHP verwenden sollen? Die Verwendung von Referenzen in foreach Schleifen kann nützlich sein, wenn Sie mit jedem Element im Array arbeiten möchten, über das Sie iterieren. Zum Beispiel:

 $arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } // $arr is now array(2, 4, 6, 8)

Das Problem ist, dass dies, wenn Sie nicht aufpassen, auch einige unerwünschte Nebenwirkungen und Folgen haben kann. Insbesondere im obigen Beispiel bleibt $value nach Ausführung des Codes im Gültigkeitsbereich und enthält einen Verweis auf das letzte Element im Array. Nachfolgende Operationen, an denen $value beteiligt ist, könnten daher unbeabsichtigt dazu führen, dass das letzte Element im Array geändert wird.

Die wichtigste Sache, die Sie sich merken sollten, ist, dass foreach keinen Gültigkeitsbereich erstellt. Somit ist $value im obigen Beispiel eine Referenz im obersten Bereich des Skripts. Bei jeder Iteration legt foreach die Referenz so fest, dass sie auf das nächste Element von $array . Nach Abschluss der Schleife zeigt $value daher immer noch auf das letzte Element von $array und bleibt im Geltungsbereich.

Hier ist ein Beispiel für die Art von ausweichenden und verwirrenden Fehlern, zu denen dies führen kann:

 $array = [1, 2, 3]; echo implode(',', $array), "\n"; foreach ($array as &$value) {} // by reference echo implode(',', $array), "\n"; foreach ($array as $value) {} // by value (ie, copy) echo implode(',', $array), "\n";

Der obige Code gibt Folgendes aus:

 1,2,3 1,2,3 1,2,2

Nein, das ist kein Tippfehler. Der letzte Wert in der letzten Zeile ist tatsächlich eine 2, keine 3.

Warum?

Nach dem Durchlaufen der ersten foreach -Schleife bleibt $array unverändert, aber wie oben erläutert, bleibt $value als freier Verweis auf das letzte Element in $array übrig (da diese foreach -Schleife auf $value per reference zugegriffen hat).

Wenn wir also die zweite foreach Schleife durchlaufen, scheinen „seltsame Dinge“ zu passieren. Da $value nun über value (dh durch copy ) zugegriffen wird, kopiert foreach jedes sequentielle $array Element in jedem Schritt der Schleife nach $value . Als Ergebnis passiert Folgendes bei jedem Schritt der zweiten foreach -Schleife:

  • Pass 1: Kopiert $array[0] (dh „1“) in $value (was eine Referenz auf $array[2] ist), sodass $array[2] jetzt gleich 1 ist. Also enthält $array jetzt [1, 2, 1].
  • Pass 2: Kopiert $array[1] (dh „2“) in $value (was eine Referenz auf $array[2] ist), sodass $array[2] jetzt gleich 2 ist. Also enthält $array jetzt [1, 2, 2].
  • Durchgang 3: Kopiert $array[2] (was jetzt „2“ entspricht) in $value (was ein Verweis auf $array[2] ist), sodass $array[2] immer noch gleich 2 ist. Also enthält $array jetzt [1 , 2, 2].

Um dennoch von den Vorteilen der Verwendung von Referenzen in foreach -Schleifen zu profitieren, ohne das Risiko solcher Probleme einzugehen, rufen unset() für die Variable direkt nach der foreach -Schleife auf, um die Referenz zu entfernen; z.B:

 $arr = array(1, 2, 3, 4); foreach ($arr as &$value) { $value = $value * 2; } unset($value); // $value no longer references $arr[3]

Häufiger Fehler Nr. 2: Missverständnis des Verhaltens isset()

Trotz seines Namens gibt isset() nicht nur false zurück, wenn ein Element nicht existiert, sondern gibt auch false für null zurück .

Dieses Verhalten ist problematischer, als es auf den ersten Blick erscheinen mag, und ist eine häufige Ursache für Probleme.

Folgendes berücksichtigen:

 $data = fetchRecordFromStorage($storage, $identifier); if (!isset($data['keyShouldBeSet']) { // do something here if 'keyShouldBeSet' is not set }

Der Autor dieses Codes wollte vermutlich überprüfen, ob keyShouldBeSet in $data gesetzt wurde. Aber wie bereits erwähnt, gibt isset($data['keyShouldBeSet']) auch false zurück, wenn $data['keyShouldBeSet'] gesetzt wurde , aber auf null gesetzt wurde. Die obige Logik ist also fehlerhaft.

Hier ist ein weiteres Beispiel:

 if ($_POST['active']) { $postData = extractSomething($_POST); } // ... if (!isset($postData)) { echo 'post not active'; }

Der obige Code geht davon aus, dass, wenn $_POST['active'] true zurückgibt, postData zwangsläufig gesetzt wird und daher isset($postData) true zurückgibt. Umgekehrt geht der obige Code davon aus, dass isset($postData) nur dann false zurückgibt, wenn $_POST['active'] ebenfalls false zurückgibt.

Nicht.

Wie bereits erläutert, isset($postData) auch false zurück, wenn $postData auf null gesetzt wurde. Daher ist es möglich, dass isset($postData) false zurückgibt, selbst wenn $_POST['active'] true zurückgibt. Auch hier ist die obige Logik fehlerhaft.

Übrigens, als Randbemerkung, wenn die Absicht im obigen Code wirklich darin bestand, erneut zu prüfen, ob $_POST['active'] wahr zurückgab, war es auf jeden Fall eine schlechte Codierungsentscheidung, sich dafür auf isset() zu verlassen. Stattdessen wäre es besser gewesen, $_POST['active'] einfach erneut zu überprüfen; dh:

 if ($_POST['active']) { $postData = extractSomething($_POST); } // ... if ($_POST['active']) { echo 'post not active'; }

In Fällen, in denen es wichtig ist, zu überprüfen, ob eine Variable wirklich gesetzt wurde (dh um zwischen einer nicht gesetzten und einer auf null gesetzten Variable zu unterscheiden), ist die Methode array_key_exists() viel robuster Lösung.

Zum Beispiel könnten wir das erste der beiden obigen Beispiele wie folgt umschreiben:

 $data = fetchRecordFromStorage($storage, $identifier); if (! array_key_exists('keyShouldBeSet', $data)) { // do this if 'keyShouldBeSet' isn't set }

Darüber hinaus können wir durch die Kombination von array_key_exists() mit get_defined_vars() zuverlässig prüfen, ob eine Variable im aktuellen Gültigkeitsbereich gesetzt wurde oder nicht:

 if (array_key_exists('varShouldBeSet', get_defined_vars())) { // variable $varShouldBeSet exists in current scope }

Häufiger Fehler Nr. 3: Verwirrung über die Rückgabe nach Referenz vs. nach Wert

Betrachten Sie dieses Code-Snippet:

 class Config { private $values = []; public function getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];

Wenn Sie den obigen Code ausführen, erhalten Sie Folgendes:

 PHP Notice: Undefined index: test in /path/to/my/script.php on line 21

Was ist falsch?

Das Problem ist, dass der obige Code die Rückgabe von Arrays nach Referenz mit der Rückgabe von Arrays nach Wert verwechselt. Sofern Sie PHP nicht explizit anweisen, ein Array als Referenz zurückzugeben (dh durch Verwendung von & ), gibt PHP das Array standardmäßig „nach Wert“ zurück. Dies bedeutet, dass eine Kopie des Arrays zurückgegeben wird und daher die aufgerufene Funktion und der Aufrufer nicht auf dieselbe Instanz des Arrays zugreifen.

Der obige Aufruf von getValues() gibt also eine Kopie des $values Arrays statt einer Referenz darauf zurück. Betrachten wir in diesem Sinne die beiden Schlüsselzeilen aus dem obigen Beispiel noch einmal:

 // getValues() returns a COPY of the $values array, so this adds a 'test' element // to a COPY of the $values array, but not to the $values array itself. $config->getValues()['test'] = 'test'; // getValues() again returns ANOTHER COPY of the $values array, and THIS copy doesn't // contain a 'test' element (which is why we get the "undefined index" message). echo $config->getValues()['test'];

Eine mögliche Lösung wäre, die erste Kopie des $values Arrays zu speichern, das von getValues() zurückgegeben wird, und diese Kopie anschließend zu bearbeiten. z.B:

 $vals = $config->getValues(); $vals['test'] = 'test'; echo $vals['test'];

Dieser Code wird gut funktionieren (dh er wird test ausgeben, ohne eine „undefinierter Index“-Meldung zu erzeugen), aber je nachdem, was Sie erreichen möchten, kann dieser Ansatz angemessen sein oder nicht. Insbesondere ändert der obige Code nicht das ursprüngliche $values Array. Wenn Sie also möchten, dass Ihre Änderungen (z. B. das Hinzufügen eines 'test'-Elements) das ursprüngliche Array beeinflussen, müssen Sie stattdessen die getValues() ändern, um eine Referenz auf das Array $values ​​selbst zurückzugeben. Dies geschieht durch Hinzufügen eines & vor dem Funktionsnamen, wodurch angezeigt wird, dass eine Referenz zurückgegeben werden soll. dh:

 class Config { private $values = []; // return a REFERENCE to the actual $values array public function &getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];

Die Ausgabe davon wird wie erwartet test sein.

Aber um die Dinge noch verwirrender zu machen, betrachten Sie stattdessen das folgende Code-Snippet:

 class Config { private $values; // using ArrayObject rather than array public function __construct() { $this->values = new ArrayObject(); } public function getValues() { return $this->values; } } $config = new Config(); $config->getValues()['test'] = 'test'; echo $config->getValues()['test'];

Wenn Sie vermutet haben, dass dies zu demselben „undefinierten Index“-Fehler führen würde wie unser früheres array Beispiel, haben Sie sich geirrt. Tatsächlich wird dieser Code gut funktionieren. Der Grund dafür ist, dass PHP im Gegensatz zu Arrays Objekte immer per Referenz übergibt . ( ArrayObject ist ein SPL-Objekt, das die Verwendung von Arrays vollständig nachahmt, aber als Objekt funktioniert.)

Wie diese Beispiele zeigen, ist es in PHP nicht immer ganz offensichtlich, ob es sich um eine Kopie oder eine Referenz handelt. Es ist daher wichtig, diese Standardverhalten zu verstehen (dh Variablen und Arrays werden als Wert übergeben; Objekte werden als Referenz übergeben) und auch die API-Dokumentation für die Funktion, die Sie aufrufen, sorgfältig zu überprüfen, um festzustellen, ob sie einen Wert zurückgibt, a Kopie eines Arrays, ein Verweis auf ein Array oder ein Verweis auf ein Objekt.

Abgesehen davon ist es wichtig zu beachten, dass die Praxis, eine Referenz auf ein Array oder ein ArrayObject , im Allgemeinen etwas ist, das vermieden werden sollte, da es dem Aufrufer die Möglichkeit gibt, die privaten Daten der Instanz zu ändern. Dies „fliegt ins Gesicht“ der Kapselung. Stattdessen ist es besser, „Getter“ und „Setter“ im alten Stil zu verwenden, z.

 class Config { private $values = []; public function setValue($key, $value) { $this->values[$key] = $value; } public function getValue($key) { return $this->values[$key]; } } $config = new Config(); $config->setValue('testKey', 'testValue'); echo $config->getValue('testKey'); // echos 'testValue'

Dieser Ansatz gibt dem Aufrufer die Möglichkeit, einen beliebigen Wert im Array festzulegen oder abzurufen, ohne öffentlichen Zugriff auf das ansonsten private $values -Array selbst bereitzustellen.

Häufiger Fehler Nr. 4: Abfragen in einer Schleife ausführen

Es ist nicht ungewöhnlich, auf so etwas zu stoßen, wenn Ihr PHP nicht funktioniert:

 $models = []; foreach ($inputValues as $inputValue) { $models[] = $valueRepository->findByValue($inputValue); }

Auch wenn hier absolut nichts falsch ist, aber wenn Sie der Logik im Code folgen, werden Sie vielleicht feststellen, dass der unschuldig aussehende Aufruf von $valueRepository->findByValue() letztendlich zu einer Art Abfrage führt, wie zum Beispiel:

 $result = $connection->query("SELECT `x`,`y` FROM `values` WHERE `value`=" . $inputValue);

Als Ergebnis würde jede Iteration der obigen Schleife zu einer separaten Abfrage an die Datenbank führen. Wenn Sie also beispielsweise ein Array mit 1.000 Werten an die Schleife übergeben, werden 1.000 separate Abfragen an die Ressource generiert! Wenn ein solches Skript in mehreren Threads aufgerufen wird, kann es möglicherweise das System zum Erliegen bringen.

Daher ist es wichtig, zu erkennen, wann Abfragen von Ihrem Code durchgeführt werden, und, wann immer möglich, die Werte zu sammeln und dann eine Abfrage auszuführen, um alle Ergebnisse abzurufen.

Ein Beispiel für einen ziemlich häufigen Ort, an dem Abfragen ineffizient (dh in einer Schleife) durchgeführt werden, ist, wenn ein Formular mit einer Liste von Werten (z. B. IDs) gesendet wird. Um dann die vollständigen Datensatzdaten für jede der IDs abzurufen, durchläuft der Code das Array und führt eine separate SQL-Abfrage für jede ID durch. Das sieht oft so aus:

 $data = []; foreach ($ids as $id) { $result = $connection->query("SELECT `x`, `y` FROM `values` WHERE `id` = " . $id); $data[] = $result->fetch_row(); }

Aber dasselbe kann viel effizienter in einer einzigen SQL-Abfrage wie folgt erreicht werden:

 $data = []; if (count($ids)) { $result = $connection->query("SELECT `x`, `y` FROM `values` WHERE `id` IN (" . implode(',', $ids)); while ($row = $result->fetch_row()) { $data[] = $row; } }

Daher ist es wichtig zu erkennen, ob Abfragen direkt oder indirekt von Ihrem Code durchgeführt werden. Sammeln Sie nach Möglichkeit die Werte und führen Sie dann eine Abfrage aus, um alle Ergebnisse abzurufen. Doch auch hier ist Vorsicht geboten, was uns zu unserem nächsten häufigen PHP-Fehler führt…

Häufiger Fehler Nr. 5: Headfakes und Ineffizienzen bei der Speichernutzung

Das gleichzeitige Abrufen vieler Datensätze ist zwar definitiv effizienter als das Ausführen einer einzelnen Abfrage für jede abzurufende Zeile, aber ein solcher Ansatz kann möglicherweise zu einem „Speichermangel“-Zustand in libmysqlclient , wenn die mysql -Erweiterung von PHP verwendet wird.

Schauen wir uns zur Demonstration eine Testbox mit begrenzten Ressourcen (512 MB RAM), MySQL und php-cli an.

Wir booten eine Datenbanktabelle wie folgt:

 // connect to mysql $connection = new mysqli('localhost', 'username', 'password', 'database'); // create table of 400 columns $query = 'CREATE TABLE `test`(`id` INT NOT NULL PRIMARY KEY AUTO_INCREMENT'; for ($col = 0; $col < 400; $col++) { $query .= ", `col$col` CHAR(10) NOT NULL"; } $query .= ');'; $connection->query($query); // write 2 million rows for ($row = 0; $row < 2000000; $row++) { $query = "INSERT INTO `test` VALUES ($row"; for ($col = 0; $col < 400; $col++) { $query .= ', ' . mt_rand(1000000000, 9999999999); } $query .= ')'; $connection->query($query); }

OK, jetzt überprüfen wir die Ressourcennutzung:

 // connect to mysql $connection = new mysqli('localhost', 'username', 'password', 'database'); echo "Before: " . memory_get_peak_usage() . "\n"; $res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 1'); echo "Limit 1: " . memory_get_peak_usage() . "\n"; $res = $connection->query('SELECT `x`,`y` FROM `test` LIMIT 10000'); echo "Limit 10000: " . memory_get_peak_usage() . "\n";

Ausgabe:

 Before: 224704 Limit 1: 224704 Limit 10000: 224704

Cool. Sieht so aus, als ob die Abfrage intern in Bezug auf Ressourcen sicher verwaltet wird.

Um sicherzugehen, erhöhen wir das Limit noch einmal und setzen es auf 100.000. Uh-oh. Wenn wir das tun, erhalten wir:

 PHP Warning: mysqli::query(): (HY000/2013): Lost connection to MySQL server during query in /root/test.php on line 11

Was ist passiert?

Das Problem hier ist die Funktionsweise des mysql -Moduls von PHP. Es ist wirklich nur ein Proxy für libmysqlclient , der die Drecksarbeit erledigt. Wenn ein Teil der Daten ausgewählt wird, geht er direkt in den Speicher. Da dieser Speicher nicht vom PHP-Manager verwaltet wird, memory_get_peak_usage() keine Zunahme der Ressourcennutzung, wenn wir das Limit in unserer Abfrage erhöhen. Dies führt zu Problemen wie dem oben gezeigten, bei dem wir in Selbstzufriedenheit getäuscht werden und denken, dass unsere Speicherverwaltung in Ordnung ist. Aber in Wirklichkeit ist unsere Speicherverwaltung ernsthaft fehlerhaft und wir können Probleme wie das oben gezeigte haben.

Sie können zumindest das obige Headfake vermeiden (obwohl es selbst Ihre Speichernutzung nicht verbessert), indem Sie stattdessen das mysqlnd -Modul verwenden. mysqlnd ist als native PHP-Erweiterung kompiliert und verwendet den Speichermanager von PHP.

Wenn wir also den obigen Test mit mysqlnd anstelle von mysql mysql , erhalten wir ein viel realistischeres Bild unserer Speicherauslastung:

 Before: 232048 Limit 1: 324952 Limit 10000: 32572912

Und es ist übrigens noch schlimmer. Laut PHP-Dokumentation verwendet mysql doppelt so viele Ressourcen wie mysqlnd , um Daten zu speichern, sodass das ursprüngliche Skript, das mysql verwendet, tatsächlich noch mehr Speicher benötigte als hier gezeigt (ungefähr doppelt so viel).

Um solche Probleme zu vermeiden, sollten Sie die Größe Ihrer Abfragen begrenzen und eine Schleife mit einer geringen Anzahl von Iterationen verwenden. z.B:

 $totalNumberToFetch = 10000; $portionSize = 100; for ($i = 0; $i <= ceil($totalNumberToFetch / $portionSize); $i++) { $limitFrom = $portionSize * $i; $res = $connection->query( "SELECT `x`,`y` FROM `test` LIMIT $limitFrom, $portionSize"); }

Wenn wir sowohl diesen PHP-Fehler als auch Fehler Nr. 4 oben betrachten, stellen wir fest, dass Ihr Code idealerweise ein gesundes Gleichgewicht zwischen zu granularen und sich wiederholenden Abfragen einerseits und jeder Ihrer Abfragen erreichen muss einzelne Abfragen zu groß sein. Wie bei den meisten Dingen im Leben ist ein Gleichgewicht erforderlich; Beide Extreme sind nicht gut und können Probleme verursachen, wenn PHP nicht richtig funktioniert.

Häufiger Fehler Nr. 6: Ignorieren von Unicode/UTF-8-Problemen

In gewisser Weise ist dies eher ein Problem in PHP selbst als etwas, auf das Sie beim Debuggen von PHP stoßen würden, aber es wurde nie angemessen angegangen. Der Kern von PHP 6 sollte Unicode-fähig gemacht werden, aber das wurde auf Eis gelegt, als die Entwicklung von PHP 6 im Jahr 2010 ausgesetzt wurde.

Aber das entbindet den Entwickler keineswegs davon, UTF-8 richtig zu handhaben und die irrige Annahme zu vermeiden, dass alle Strings zwangsläufig „plain old ASCII“ sein werden. Code, der Nicht-ASCII-Strings nicht richtig verarbeitet, ist berüchtigt dafür, knorrige Heisenbugs in Ihren Code einzuführen. Selbst einfache strlen($_POST['name']) Aufrufe könnten Probleme verursachen, wenn jemand mit einem Nachnamen wie „Schrödinger“ versucht, sich in Ihrem System anzumelden.

Hier ist eine kleine Checkliste, um solche Probleme in Ihrem Code zu vermeiden:

  • Wenn Sie nicht viel über Unicode und UTF-8 wissen, sollten Sie sich zumindest die Grundlagen aneignen. Hier gibt es eine tolle Grundierung.
  • Stellen Sie sicher, dass Sie immer die mb_* Funktionen anstelle der alten String-Funktionen verwenden (stellen Sie sicher, dass die „Multibyte“-Erweiterung in Ihrem PHP-Build enthalten ist).
  • Stellen Sie sicher, dass Ihre Datenbank und Tabellen auf die Verwendung von Unicode eingestellt sind (viele Builds von MySQL verwenden immer noch standardmäßig latin1 ).
  • Denken Sie daran, dass json_encode() Nicht-ASCII-Symbole konvertiert (z. B. „Schrödinger“ wird zu „Schr\u00f6dinger“), serialize() jedoch nicht .
  • Stellen Sie sicher, dass Ihre PHP-Codedateien ebenfalls UTF-8-codiert sind, um Kollisionen beim Verketten von Zeichenfolgen mit fest codierten oder konfigurierten Zeichenfolgenkonstanten zu vermeiden.

Eine besonders wertvolle Ressource in dieser Hinsicht ist der Post UTF-8 Primer for PHP and MySQL von Francisco Claria in diesem Blog.

Häufiger Fehler Nr. 7: Angenommen, $_POST enthält immer Ihre POST-Daten

Trotz seines Namens enthält das $_POST Array nicht immer Ihre POST-Daten und kann leicht leer gefunden werden. Um dies zu verstehen, schauen wir uns ein Beispiel an. Angenommen, wir stellen eine Serveranfrage mit einem Aufruf von jQuery.ajax() wie folgt:

 // js $.ajax({ url: 'http://my.site/some/path', method: 'post', data: JSON.stringify({a: 'a', b: 'b'}), contentType: 'application/json' });

(Beachten Sie hier übrigens den contentType: 'application/json' . Wir senden Daten als JSON, was für APIs recht beliebt ist. Dies ist beispielsweise der Standard für das Posten im AngularJS $http -Dienst.)

Auf der Serverseite unseres Beispiels geben wir einfach das Array $_POST :

 // php var_dump($_POST);

Überraschenderweise wird das Ergebnis sein:

 array(0) { }

Warum? Was ist mit unserem JSON-String {a: 'a', b: 'b'} passiert?

Die Antwort ist, dass PHP eine POST-Nutzlast nur dann automatisch parst, wenn sie einen Inhaltstyp von application/x-www-form-urlencoded oder multipart/form-data . Die Gründe dafür sind historischer Natur – diese beiden Inhaltstypen waren im Wesentlichen die einzigen, die vor Jahren verwendet wurden, als $_POST PHP implementiert wurde. Bei jedem anderen Inhaltstyp (selbst bei den heute recht beliebten wie application/json ) lädt PHP die POST-Payload nicht automatisch.

Da $_POST ein Superglobal ist, ist der geänderte Wert (dh einschließlich der POST-Nutzdaten) in unserem gesamten Code referenzierbar, wenn wir ihn einmal überschreiben (vorzugsweise früh in unserem Skript). Dies ist wichtig, da $_POST häufig von PHP-Frameworks und fast allen benutzerdefinierten Skripten verwendet wird, um Anforderungsdaten zu extrahieren und umzuwandeln.

Wenn wir also beispielsweise eine POST-Nutzlast mit dem Inhaltstyp application/json verarbeiten, müssen wir den Anforderungsinhalt manuell parsen (d. h. die JSON-Daten decodieren) und die Variable $_POST wie folgt überschreiben:

 // php $_POST = json_decode(file_get_contents('php://input'), true);

Wenn wir dann das $_POST Array ausgeben, sehen wir, dass es die POST-Nutzlast korrekt enthält; z.B:

 array(2) { ["a"]=> string(1) "a" ["b"]=> string(1) "b" }

Häufiger Fehler Nr. 8: Denken, dass PHP einen Zeichendatentyp unterstützt

Sehen Sie sich dieses Beispielcode an und versuchen Sie zu erraten, was es ausgeben wird:

 for ($c = 'a'; $c <= 'z'; $c++) { echo $c . "\n"; }

Wenn Sie mit „a“ bis „z“ geantwortet haben, werden Sie vielleicht überrascht sein, dass Sie sich geirrt haben.

Ja, es wird 'a' bis 'z' gedruckt, aber dann auch 'aa' bis 'yz'. Mal sehen warum.

In PHP gibt es keinen char -Datentyp; Es ist nur eine string verfügbar. Vor diesem Hintergrund ergibt das Erhöhen des string z in PHP aa :

 php> $c = 'z'; echo ++$c . "\n"; aa

Um die Sache noch weiter zu verwirren, ist aa lexikografisch kleiner als z :

 php> var_export((boolean)('aa' < 'z')) . "\n"; true

Aus diesem Grund gibt der oben dargestellte Beispielcode die Buchstaben a bis z aus, aber dann auch aa bis yz . Es stoppt, wenn es za erreicht, was der erste Wert ist, auf den es stößt, dass es „größer als“ z ist:

 php> var_export((boolean)('za' < 'z')) . "\n"; false

In diesem Fall ist hier eine Möglichkeit, die Werte "a" bis "z" in PHP ordnungsgemäß zu durchlaufen:

 for ($i = ord('a'); $i <= ord('z'); $i++) { echo chr($i) . "\n"; }

Oder alternativ:

 $letters = range('a', 'z'); for ($i = 0; $i < count($letters); $i++) { echo $letters[$i] . "\n"; }

Häufiger Fehler Nr. 9: Ignorieren von Codierungsstandards

Obwohl das Ignorieren von Codierungsstandards nicht direkt dazu führt, dass PHP-Code debuggt werden muss, ist es wahrscheinlich dennoch eines der wichtigsten Dinge, die hier diskutiert werden müssen.

Das Ignorieren von Codierungsstandards kann eine ganze Reihe von Problemen in einem Projekt verursachen. Im besten Fall führt dies zu inkonsistentem Code (da jeder Entwickler „sein eigenes Ding macht“). Aber im schlimmsten Fall produziert es PHP-Code, der nicht funktioniert oder schwierig (manchmal fast unmöglich) zu navigieren ist, was es extrem schwierig macht, ihn zu debuggen, zu verbessern und zu warten. Und das bedeutet reduzierte Produktivität für Ihr Team, einschließlich viel verschwendeter (oder zumindest unnötiger) Mühe.

Zum Glück für PHP-Entwickler gibt es die PHP Standards Recommendation (PSR), die aus den folgenden fünf Standards besteht:

  • PSR-0: Autoloading-Standard
  • PSR-1: Grundlegender Codierungsstandard
  • PSR-2: Coding Style Guide
  • PSR-3: Logger-Schnittstelle
  • PSR-4: Autoloader

PSR wurde ursprünglich basierend auf Beiträgen von Betreuern der bekanntesten Plattformen auf dem Markt erstellt. Zend, Drupal, Symfony, Joomla und andere haben zu diesen Standards beigetragen und folgen ihnen nun. Sogar PEAR, das sich davor jahrelang bemühte, ein Standard zu sein, nimmt jetzt an PSR teil.

In gewisser Weise spielt es fast keine Rolle, was Ihr Codierungsstandard ist, solange Sie sich auf einen Standard einigen und sich daran halten, aber das Befolgen des PSR ist im Allgemeinen eine gute Idee, es sei denn, Sie haben einen zwingenden Grund für Ihr Projekt, etwas anderes zu tun . Immer mehr Teams und Projekte sind PSR-konform. Tt wird zu diesem Zeitpunkt von der Mehrheit der PHP-Entwickler definitiv als „der“ Standard anerkannt, so dass die Verwendung dazu beitragen wird, dass neue Entwickler mit Ihrem Programmierstandard vertraut und vertraut sind, wenn sie Ihrem Team beitreten.

Häufiger Fehler Nr. 10: Missbrauch von empty()

Einige PHP-Entwickler verwenden gerne empty() für boolesche Prüfungen für so gut wie alles. Es gibt jedoch Fälle, in denen dies zu Verwirrung führen kann.

Kommen wir zunächst auf Arrays und ArrayObject Instanzen zurück (die Arrays imitieren). Angesichts ihrer Ähnlichkeit ist es leicht anzunehmen, dass sich Arrays und ArrayObject Instanzen identisch verhalten. Dies erweist sich jedoch als gefährliche Annahme. Zum Beispiel in PHP 5.0:

 // PHP 5.0 or later: $array = []; var_dump(empty($array)); // outputs bool(true) $array = new ArrayObject(); var_dump(empty($array)); // outputs bool(false) // why don't these both produce the same output?

Und um die Sache noch schlimmer zu machen, wären die Ergebnisse vor PHP 5.0 anders ausgefallen:

 // Prior to PHP 5.0: $array = []; var_dump(empty($array)); // outputs bool(false) $array = new ArrayObject(); var_dump(empty($array)); // outputs bool(false)

Dieser Ansatz ist leider sehr beliebt. Beispielsweise gibt Zend\Db\TableGateway von Zend Framework 2 auf diese Weise Daten zurück, wenn current() für das Ergebnis von TableGateway::select() wie das Dokument vorschlägt. Entwickler können mit solchen Daten leicht Opfer dieses Fehlers werden.

Um diese Probleme zu vermeiden, ist der bessere Ansatz zum Prüfen auf leere Array-Strukturen die Verwendung von count() :

 // Note that this work in ALL versions of PHP (both pre and post 5.0): $array = []; var_dump(count($array)); // outputs int(0) $array = new ArrayObject(); var_dump(count($array)); // outputs int(0)

Da PHP 0 in false , kann count() übrigens auch innerhalb von if () Bedingungen verwendet werden, um nach leeren Arrays zu suchen. Es ist auch erwähnenswert, dass count() in PHP eine konstante Komplexität ( O(1) -Operation) auf Arrays ist, was noch deutlicher macht, dass es die richtige Wahl ist.

Ein weiteres Beispiel, wenn empty() gefährlich sein kann, ist die Kombination mit der magischen Klassenfunktion __get() . Lassen Sie uns zwei Klassen definieren und in beiden eine test Eigenschaft haben.

Lassen Sie uns zuerst eine Regular Klasse definieren, die test als normale Eigenschaft enthält:

 class Regular { public $test = 'value'; }

Dann definieren wir eine Magic -Klasse, die den magischen __get() Operator verwendet, um auf ihre test Eigenschaft zuzugreifen:

 class Magic { private $values = ['test' => 'value']; public function __get($key) { if (isset($this->values[$key])) { return $this->values[$key]; } } }

Sehen wir uns nun an, was passiert, wenn wir versuchen, auf die Eigenschaft test jeder dieser Klassen zuzugreifen:

 $regular = new Regular(); var_dump($regular->test); // outputs string(4) "value" $magic = new Magic(); var_dump($magic->test); // outputs string(4) "value"

Gut so weit.

Aber jetzt sehen wir uns an, was passiert, wenn wir für jedes dieser Elemente empty() aufrufen:

 var_dump(empty($regular->test)); // outputs bool(false) var_dump(empty($magic->test)); // outputs bool(true)

Pfui. Wenn wir uns also auf empty() verlassen, können wir fälschlicherweise glauben, dass die Eigenschaft test von $magic leer ist, obwohl sie in Wirklichkeit auf 'value' gesetzt ist.

Wenn eine Klasse die magische __get() Funktion verwendet, um den Wert einer Eigenschaft abzurufen, gibt es leider keine narrensichere Methode, um zu überprüfen, ob dieser Eigenschaftswert leer ist oder nicht. Außerhalb des Gültigkeitsbereichs der Klasse können Sie eigentlich nur prüfen, ob ein null zurückgegeben wird, was nicht unbedingt bedeutet, dass der entsprechende Schlüssel nicht gesetzt ist, da er tatsächlich auf null hätte gesetzt werden können.

Wenn wir dagegen versuchen, auf eine nicht vorhandene Eigenschaft einer Regular Klasseninstanz zu verweisen, erhalten wir eine Benachrichtigung ähnlich der folgenden:

 Notice: Undefined property: Regular::$nonExistantTest in /path/to/test.php on line 10 Call Stack: 0.0012 234704 1. {main}() /path/to/test.php:0

Der Hauptpunkt hier ist also, dass die Methode empty() mit Vorsicht verwendet werden sollte, da sie zu verwirrenden – oder sogar potenziell irreführenden – Ergebnissen führen kann, wenn man nicht aufpasst.

Einpacken

Die Benutzerfreundlichkeit von PHP kann Entwickler in ein falsches Gefühl der Bequemlichkeit wiegen und sie aufgrund einiger Nuancen und Eigenheiten der Sprache anfällig für langwierige PHP-Debugging machen. Dies kann dazu führen, dass PHP nicht funktioniert und Probleme wie die hier beschriebenen auftreten.

Die PHP-Sprache hat sich im Laufe ihrer 20-jährigen Geschichte erheblich weiterentwickelt. Sich mit seinen Feinheiten vertraut zu machen, ist ein lohnendes Unterfangen, da es dazu beiträgt, dass die von Ihnen erstellte Software skalierbarer, robuster und wartbarer ist.