Posts Tagged ‘PHP’

Samstag, 27. August 2016

PHP-Fehlermeldungen ausser E_DEPRECATED loggen

Unter Apache (.htaccess oder VirtualHost-Konfiguration) stellt man dies folgendermassen ein:

...
php_value error_reporting 24575
...

Den ersten Hinweis lieferte How to disable deprecated messages in Joomla?. Doch die dort angegebene Konstante (22527) ist überholt, weil PHP 7 neue Fehlerkonstanten mitbringt, welche noch hinzuaddiert werden müssen.

Hierfür habe ich mich der Liste unter Error Handling — Predefined Constants bedient, und

32767 - 8192 = 24575

berechnet.

Tags: , , , , ,
Labels: Linux

Keine Kommentare | neuen Kommentar verfassen

Mittwoch, 3. Juni 2015

PHP erlaubt keine Floats als Array-Schlüssel

Da programmiere ich seit bald 15 Jahren mit PHP, doch erst vor wenigen Wochen habe ich realisiert, dass Arrays keine Floats als Schlüssel unterstützen. Variablen mit Fliesskommazahlen können zwar durchaus als Schlüssel gesetzt werden, werden in der Folge aber in Integer-Werte umgewandelt. Dies führt dazu, dass Schlüssel wie ‚3.3‘ sowie ‚3.4‘ zu ‚3‘ umgewandelt werden und der Variablenwert dementsprechend überschrieben wird:

Working with an array with periods in key values

Wer die Fliesskommazahlen zwingend benötigt, muss diese zuerst in Strings umwandeln:

...
$key = 3.3;
$keyValid = (string)$key;
$array[$key] = 'One';
$array[$keyValid] = 'Two';
var_dump($array);
...

Via: ToString() equivalent in PHP

ergibt:

array(2) {
  [3]=>
  string(3) "One"
  ["3.3"]=>
  string(3) "Two"
}

Tags: , , , , , , , ,
Labels: Uncategorized

Keine Kommentare | neuen Kommentar verfassen

Sonntag, 14. Dezember 2014

MySQL-Query auf Dev blitzschnell, in Prod extrem langsam

Am Freitag habe ich mich entschieden, eine bisher nur auf meinem lokalen Mac mini in einem Vagrant-Container laufende Web-Applikation auf meinen „produktiven“ Web-Server bei der Cyon GmbH zu verschieben.

Es handelt sich um eine Applikation, mit welcher Fragen einer Zertifizierungsprüfung mittels Multiple Choice beantwortet werden können. Eine Unterseite der Applikation berechnet aus dem Log der bisher getätigten Antworten Statistiken.

Nach einigen Minuten lief die Web-Applikation und der aktuellste MySQL-Dump war ebenfalls auf dem Server eingespielt. Doch dann kam das böse Erwachen: Beim Aufruf einer neuen Frage wartete ich unzählige Sekunden auf eine Antwort des Servers, bis die Ausführung des PHP-Scripts dann mit einem Timeout abbrach.

Was zum Teufel? Rasch war klar, dass ein SQL-Query zur Berechnung von Statistiken der Übeltäter war.

Auf Dev lief das MySQL-Query folgendermassen rasch durch:

[12-Dec-2014 13:22:18 Europe/Zurich] I - mysql->query() took 0.1694 secs in /var/www/apps/%APP%/inc/%APP%.class.php:343 for URI (Referer: unknown) from IP 192.168.1.1 with User Agent "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/600.1.17 (KHTML, like Gecko) Version/7.1 Safari/537.85.10"

Auf Prod hingegen loggte ich diesen Zeitwert:

[12-Dec-2014 12:40:18 Europe/Zurich] I - mysql->query() took 47.8917 secs in %PATH%%APP%.class.php:343 for URI <%URI%> (Referer: %REFERER%) from IP %IP% with User Agent "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/600.1.17 (KHTML, like Gecko) Version/7.1 Safari/537.85.10"

Eine Verlangsamung von mehr als dem 200-fachen!

Als erstes prüfte ich die Versionen der Datenbank-Software:

Dev

-- Server version	5.6.21-1~dotdeb.1

Prod

-- Server version	5.5.40-cll

Ich verwendete also eine Zehntelsversion bessere Datenbank.

Anschliessend liess ich mir das Query sowohl in Dev als auch in der Produktion „erklären“ (EXPLAIN):

Dev

EXPLAIN-localhost
image-6013

Prod

EXPLAIN-cyon
image-6014

Der Unterschied: Auf Prod führte MySQL ein „Dependent Subquery“ aus, auf Dev „nur“ (?) ein „Subquery“. War das das Problem?

Da mir EXPLAIN hier nun wirklich nicht weiterhalf, wendete ich mich Google zu. Folgende zwei Stackexchange-Artikel erheischten meine Aufmerksamkeit:

Beim Lesen der Antworten kam mir plötzlich die Idee: Vielleicht sind INDEXe deine Freunde? Ich wählte die Spalte Questions-id (`Questions-id` varchar(64) NOT NULL, CLSIDs enthaltend) aus und versah sie mit einem Index.

Und siehe da, beim nächstem Aufruf der Web-Seite wurde folgender Zeitwert registriert:

[12-Dec-2014 23:33:09 Europe/Zurich] I - mysql->query() took 0.0412 secs in %PATH%%APP%.class.php:343 for URI <%URI%> (Referer: %REFERER%) from IP %IP% with User Agent "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/600.1.17 (KHTML, like Gecko) Version/7.1 Safari/537.85.10"

Der zusätzliche Index auf einer Spalte auf hat mein Performance-Problem zu meiner vollsten Zufriedenheit gelöst. Wieso der Index aber in Version 5.6 von MySQL nicht mehr benötigt wird, ist mir derzeit noch ein Rätsel. Wahrscheinlich haben die Entwickler die Subquery-Performance in dieser Version stark verbessert?

Tags: , , , , , , , , , , ,
Labels: Web

Keine Kommentare | neuen Kommentar verfassen

Sonntag, 29. Juni 2014

PHP kann unter Mac OS X nicht mit MySQL kommunizieren

Da war ich am Donnerstag in den beeindruckenden neuen Räumlichkeiten meines ehemaligen Arbeitgebers Liip und nahm am Hackday teil, wo ich mich anfänglich mit der Konfiguration von Docker befasste — und war dann auf meinem MacBook Air in einem anderen Zusammenhang mit Verbindungsproblemen zwischen PHP und der MySQL-Datenbank konfrontiert.

PHPs mysqli meldete:

Error: 2002 - No such file or directory

Das Problem lag gemäss dieser Diskussion auf Stack Overflow darin begründet, dass ich nach dem Upgrade auf Mavericks die mitgelieferte php.ini unter /etc/php.ini verwendete, welche für Mac OS X nicht anwendbare Standardwerte für die Verbindung zu MySQL enthielt.

Nachdem ich die Einträge

...
pdo_mysql.default_socket=/var/mysql/mysql.sock
mysql.default_socket = /var/mysql/mysql.sock
mysqli.default_socket = /var/mysql/mysql.sock
...

in

...
pdo_mysql.default_socket=/tmp/mysql.sock
mysql.default_socket = /tmp/mysql.sock
mysqli.default_socket = /tmp/mysql.sock
...

geändert hatte und Apache mittels

# apachectl graceful

neugestartet hatte, sprach PHP problemlos mit MySQL.

Solche Handstände sind künftig nicht mehr nötig, da ich nun endlich meine Vagrant-Installation vom Mac mini hier auf das MacBook transferiert habe.

Tags: , , , ,
Labels: Uncategorized

Keine Kommentare | neuen Kommentar verfassen

Montag, 13. Januar 2014

Das PHP Error-Log von einem Cyon-Server täglich per E-Mail zusenden

Ein guter Web-Entwickler hält das PHP Error-Log seiner Web-Sites und -Applikationen stets im Auge. Ich persönlich habe mir zum Ziel gesetzt, dass nur ein leeres Log ein gutes Log ist. Dies bedeutet demzufolge, dass man auf Produktivsystemen sauberen Code ausliefert. Und sollten doch einmal Fehlermeldungen, Warnungen und Infos im PHP Error-Log auftauchen, gilt es diese zeitnah zu beheben.

Damit man eine Web-Präsenz, welche auf einem Cyon-Server mit SSH-Zugang läuft, überwachen kann, sind folgende Anpassungen nötig:

php.ini

Bei meinen Hostings befindet sich diese Konfigurationsdatei unter ~/etc/php_settings/default/php.ini. In dieser Datei sollten die folgenden Parameter gesetzt sein:

...
error_log = /home/%CYON-ACCOUNT%/var/log/php.err
...
error_reporting = E_ALL # Allenfalls auch & ~E_DEPRECATED
...

Dabei sollte man sicherstellen, dass das Verzeichnis /home/>benutzernamen>/var/log/ existiert und schreibbar ist.

mail-php-error-log.sh

Nachfolgendes Script sorgt dafür, dass der Inhalt der heutigen php.err per E-Mail an eine frei definierbare E-Mail-Adresse gesendet wird. Nach dem Versand wird die php.err kopiert und das Original danach gelöscht. Dem Dateinamen der Kopie wird dabei der aktuelle Tag des Jahres (1 bis 365) angehängt, womit wir eine Art automatisiertes logrotate durchführen:

#!/bin/sh

LOG="/home/%CYON-ACCOUNT%/var/log/php.err"

if [ ! -f "$LOG" ]
then
        echo "File '$LOG' not found"
        exit 1
fi

LINES=`cat "$LOG" | wc -l`

if [ $LINES -lt 1 ]
then
        exit 0
fi

#echo "Since log file is not empty ($LINES lines) I'm now posting its content to the webmaster"
cat "$LOG" | mail -s "php.err" logger@domain.tld
#echo "Would now be sending mail"

# Secret Sauce
DAYOFYEAR=$(date +%j)
cp "$LOG" "$LOG.$DAYOFYEAR"
rm "$LOG"

exit 0

Dieses Script habe ich in meinem Home-Folder abgelegt und mittels chmod 700 für meinen Benutzer ausführbar gemacht.

crontab

Schlussendlich richtet man sich noch einen Eintrag in crontab ein, damit das Script automatisiert einmal im Tag aufgerufen wird:

$ crontab -e

Der Inhalt dieser Datei lautet:

MAILTO="logger@domain.tld"
...
12 23 * * * /home/%CYON-ACCOUNT%/mail-php-error-log.sh

Tags: , , , , , ,
Labels: Linux

Keine Kommentare | neuen Kommentar verfassen

Donnerstag, 19. Dezember 2013

Hochzeit-Fotos von ichwuensche.ch automatisiert herunterladen

Heuer war es wieder mal soweit — Bekannte, welche im September 2013 geheiratet und an deren Hochzeit Stephanie und ich teilgenommen haben, haben die Fotos über ichwuensche.ch zugänglich gemacht. Da es mir zu blöd war, mich durch die 12 Web-Seiten voller Hochzeitsbilder zu klicken, habe ich mir ein kleines PHP-Script geschrieben, welches mir die URLs der Fotos generiert. Dafür benötige ich die fortlaufende Nummer des ersten sowie letzten Bildes der Serie; das Script generiert anschliessend automatisiert alle URLs.

<?php
    $base = 'http://www.ichwuensche.ch/images/gallery/';
    $start = 1269245;
    $end = 1269774;
    $suffix = '_l.jpg';
    
    $counter = 0;
    for($i = $start; $i <= $end; $i++) {
        $counter++;
        
        // $middle = 4/4/2/9/6/ if JPEG basename is 1269245
        $middle = strrev($i);
        $middle = substr($middle,0,5);
        $middle = implode('/',str_split($middle)) . '/';
        
        $url = $base . $middle. $i . $suffix;
        echo $url . "\n";
    }
    
    echo "\n" . $counter . "\n";
?>

Anschliessend führt man das Script aus, pipet die Ausgabe in eine Textdatei und lässt dann wget seine Arbeit verrichten:

$ php generate-urls.php > urls.txt
$ wget -i urls.txt
--2013-12-19 16:06:37--  http://www.ichwuensche.ch/images/gallery/4/7/7/9/6/1269774_l.jpg
Verbindungsaufbau zu www.ichwuensche.ch (www.ichwuensche.ch)|85.158.232.45|:80... verbunden.
HTTP-Anforderung gesendet, warte auf Antwort... 200 OK
Länge: 150027 (147K) [image/jpeg]
In »»1269774_l.jpg«« speichern.
...

ACHTUNG: Leider haben die Entwickler der Web-Site nicht viel überlegt: Unter den Total 530 Fotos findet sich auch die Hochzeit eines unbekannten Paares aus dem Thurgau, eine Amerika-Reise eines anderen, ebenfalls unbekannten Paares sowie die wohl fast schon obligatorischen Katzenfotos.

Umgekehrt bedeutet dies, dass ein Script-Kiddie in einer Nacht alle Fotos der Web-Site abräumen könnte. Na dann.

Tags: , , , , , ,
Labels: Web

7 Kommentare | neuen Kommentar verfassen

Mittwoch, 18. Dezember 2013

Den Netatmo PHP API-Client mit weniger strikten SSL-Anforderungen patchen

Vor einigen Tagen hörte mein Raspberry Pi-Dashboard auf, die Werte meiner Netatmo NWS01 Wetterstation für Apple iPhone und Android anzuzeigen.

Auf meinem lokalen Mac funktionierte das Dashboard hingegen problemlos; d.h. ich konnte mittels dem Netatmo PHP API-Client die JSON-Datei mit den aktuellen Messwerten wie Temperatur, Luftdruck und -feuchtigkeit abrufen.

Die genaue Ursache hinter dem Problem kenne ich bis heute nicht, doch ich vermute mit dem jetzigen Wissensstand, dass die Cyon-Ingenieure an der Konfiguration ihrer Server herumgewerkelt haben und dabei unter anderem das Root-Zertifikat entfernt haben, welches der Netatmo API-Client zur HTTPS-verschlüsselten Kommunikation mit den Netatmo-Servern verwendet.

Nachdem ich nämlich die Exception mittels vardump() genauer betrachtete, welche NACurlErrorType zurücklieferte, war der Fall schnell sonnenklar:

...
[message:protected] => SSL peer certificate or SSH remote key was not OK
...

Nun … gut! Was macht man da? Ich habe die Datei NAApiClient.php gepatcht, indem ich cURL mit der auf false gesetzten Option CURLOPT_SSL_VERIFYHOST sage, unverifizierte SSL-Zertifikate kommentarlos zu akzeptieren:

...
        else 
        {
            $opts[CURLOPT_HTTPHEADER] = array('Expect:');
        }
        
        $opts[CURLOPT_SSL_VERIFYHOST] = false;
        
        curl_setopt_array($ch, $opts);
...

Bei einer API wie Netatmo ist diese manuell herbeigeführte Schwachstelle zu verantworten. Ginge es um Mailverkehr oder Online-Banking, würde ich eine solche Option definitiv nicht aktivieren.

Tags: , , , , , , , ,
Labels: Programmierung

Keine Kommentare | neuen Kommentar verfassen

Mittwoch, 23. Oktober 2013

Mit Scripts generierte ICS-Dateien mit dem korrekten Zeichensatz in Google Calendar anzeigen

Folgender kleiner Text (hier: in einem PHP-Script) hilft:

...
header('Content-Type: text/calendar; charset=utf-8');
header('Content-Disposition: inline; filename="calendar.ics"');
...

Gibt man das Charset nicht explizit an, erscheinen in Google Calendar statt den Sonderzeichen schwarze Rhomben mit einem Fragezeichen: �

Via: Character encoding in external public calendars – Why is Danish UTF-8 character not show correctly?

Tags: , ,
Labels: Programmierung

Keine Kommentare | neuen Kommentar verfassen

Montag, 7. Oktober 2013

Mit Google Docs Spreadsheet eine Variable von einer Internet-Seite abrufen und als Zellwert verwenden

In Google Docs Spreadsheet ist es äusserst einfach, eigene Zellfunktionen zu definieren und diese danach mit einer JavaScript-basierten Funktion zu hinterlegen.

Ich habe mir diese Funktionalität zu Nutzen gemacht, um via meinen Linux-Server die aktuellen Gold- und Silberpreise in Schweizer Franken von einer Web-Site auszulesen und als „rohe“ Zahl an eine Zelle eines Google Docs Spreadsheet weiterzugeben. Basierend auf diesem Wert werden danach weitere Berechnungen in anderen Zellen angestossen.

Zuerst habe ich hierzu ein Spreadsheet eröffnet und sogleich unter Tools > Script editor… ein Script namens „Code.gs“ erstellt. Dort habe ich folgende (hier anonymisierte) JavaScript-Funktion abgelegt:

function getQuote(item) {
  if(item.length < 3) {
    return '#ERROR: parameter too short';
  }
  
  var response = UrlFetchApp.fetch("http://site.tld/index.php?item=" + item + "&raw=true");
  
  quoteRaw = response.getContentText();
  quote = parseFloat(quoteRaw).toFixed(2);
  
  return quote;
}

Anschliessend habe ich in der Zelle A1 den Wert „Gold“ hinterlegt. In Zelle A2 habe ich notiert „=getQuote(A1)“. Sobald man die Zelle A2 verlässt, versucht Google, die URL aufzurufen und die Antwort in eine Zahl umzuwandeln. Dies setzt natürlich voraus, dass das PHP-Script auf dem entfernten keinen HTML-Code zurückgibt, sondern Plaintext. Sobald Google die Funktion getQuote() aufruft, steht in der Zelle für ein bis zwei Sekunden „Thinking…“, während Google im Hintergrund meinen Server kontaktiert und die hinterlegte URL aufruft. Sobald der Wert empfangen wurde, wird dieser in der Zelle angezeigt.

Nebenbemerkung: Ja, den Umweg über meinen eigenen Server könnte ich mir selbstverständlich sparen. Ich bin mir es aber zu Schade, a) ausgedehntes JavaScript zu programmieren, zumal b) Google keine guten Debug-Werkzeuge im Script-Editor bereitstellt. Stattdessen verlasse ich mich auf PHP, Apache und auf dem Server installierte Linux-Tools mit ihren ausgefeilten Funktionen und einem sauberen Debug Logging.

Tags: , , , , ,
Labels: Programmierung

Keine Kommentare | neuen Kommentar verfassen

Montag, 7. Oktober 2013

Ein PHP-Script so geschwätzig wie möglich machen

<?php
	ini_set('error_reporting',E_ALL);
	ini_set('display_errors',1);

	...
?>

Tags: , , , , ,
Labels: Programmierung

Keine Kommentare | neuen Kommentar verfassen