PHP

Installation

CentOS 8

Aus dem Remi-Repo:

dnf -y module reset php

# PHP 7.4
dnf -y module install php:remi-7.4

# PHP 8.0
dnf -y module install php:remi-8.0

# this is how to additional packages
dnf -y install php-mbstring

Alternativ (und älter) aus dem EPEL-Repo:

dnf -y module reset php

# PHP 7.3
dnf module enable php:7.3

# this is how to additional packages
dnf -y install php-mbstring
CentOS 7

Aus dem Remi-Repo:

# PHP 7.4
yum-config-manager --disable 'remi-php*'
yum-config-manager --enable remi-php74
yum -y install php

# this is how to additional packages
yum -y install php-mbstring

Der integrierte Webserver

Start:

php -S localhost:8080 path/to/my/project.php

PHP-FPM

PHP-FPM (FastCGI Process Manager) ist eine alternative PHP FastCGI-Implementierung, die eine Reihe zusätzlicher Funktionen enthält.

Statusseite aktivieren:

/etc/php-fpm.d/www.conf
listen = /run/php-fpm/www.sock
pm.status_path = /fpm-status
/etc/httpd/conf/httpd.conf
<LocationMatch "/fpm-status">
    Require local
    ProxyPass unix:/run/php-fpm/www.sock|fcgi://localhost/fpm-status
</LocationMatch>
curl 'http://localhost/fpm-status?json&full'

Ping-Seite aktivieren:

/etc/php-fpm.d/www.conf
ping.path = /fpm-ping
/etc/httpd/conf/httpd.conf
<Location "/fpm-ping">
    Require local
    ProxyPass unix:/run/php-fpm/www.sock|fcgi://localhost/fpm-ping
</Location>
curl 'http://localhost/fpm-ping'

Test der Konfigurationsdatei:

php-fpm --test

PHP-FPM Process Hintergründe und Calculator:

Composer

Composer ist ein Paketmanager für PHP >= 5.3.2, wird über die Kommandozeile ausgeführt und installiert auch notwendige Abhängigkeiten.

Installation - aus dem remi-Repo (bevorzugt):

yum -y install composer

Installation - manuell von der Composer-Webseite:

wget --output-document=composer https://getcomposer.org/composer.phar
chmod +x composer

Update:

composer update

Paket installieren - direkt per composer (bevorzugt):

composer require graylog2/gelf-php

Paket installieren - per manuellem Eintrag in der composer.json:

composer.json
{
    "require": {
        "graylog2/gelf-php": "^1.7"
    }
}

Paket mit Abhängigkeiten entfernen:

composer remove graylog2/gelf-php --update-with-dependencies

Logging

So loggt man per GELF gegen einen Graylog:

<?php

$graylog_hostname = '10.74.143.6';
$graylog_port = '12201';
$graylog_facility = 'my-facility-name';


require_once __DIR__ . '/vendor/autoload.php';

// We need a transport - UDP via port 12201 is standard.
$transport = new Gelf\Transport\UdpTransport(
    $graylog_hostname,
    $graylog_port,
    Gelf\Transport\UdpTransport::CHUNK_SIZE_LAN
);

$publisher = new Gelf\Publisher();
$publisher->addTransport($transport);


// The implementation of PSR-3 is encapsulated in the Logger-class.
// It provides high-level logging methods, such as alert(), info(), etc.
$logger = new Gelf\Logger($publisher, $graylog_facility);

// Now we can log...
$logger->emergency('My Emergency');
$logger->alert('My Alert');
$logger->critical('My Critical');
$logger->error('My Error');
$logger->warning('My Warning');
$logger->notice('My Notice');
$logger->info('My Info');
$logger->debug('My Debug');

Wie erhält man im Log neben dem Stacktrace auch die Parameter einer Funktion und deren Werte, wenn diese beim Aufruf mit einer Exception abbricht? Dafür gibt es keine php.ini-Einstellung - hier hilft nur die Definition eines eigenen Error-Handlers:

<?php
// user-defined error handler
function myErrorHandler($errno, $errstr, $errfile, $errline)
{
    // print all the stacktrace details including function parameters and values
    print_r(debug_backtrace());
    return false;
}

// function that causes an error
function exampleFunction($param1, $param2)
{
    $undefinedVar->callUndefinedFunction();
}


set_error_handler('myErrorHandler');
exampleFunction('linuxfabrik', 2016);

Keycloak (OAuth)

PHP-Applikationen können sich mit Hilfe des PHP League’s OAuth 2.0 Client per OAuth identifizieren. Hier finden sich die offiziellen Provider (Facebook & Co.), hier die Liste der Third-Party Provider.

Keycloak wird durch den Third-Party Provider stevenmaguire/oauth2-keycloak unterstützt, der sich per composer require stevenmaguire/oauth2-keycloak installieren lässt.

Für die Auswertung in PHP-Applikationen: Apache setzt Umgebungsvariablen, die sich in $_SERVER finden:

[OIDC_access_token] => eyJhbGciOiJ...Wpb8YpL8J4w
[OIDC_access_token_expires] => 1507893465
[OIDC_CLAIM_acr] => 0
[OIDC_CLAIM_aud] => example
[OIDC_CLAIM_auth_time] => 1507892808
[OIDC_CLAIM_azp] => example
[OIDC_CLAIM_email] => info@linuxfabrik.ch
[OIDC_CLAIM_exp] => 1507893465
[OIDC_CLAIM_family_name] =>
[OIDC_CLAIM_given_name] =>
[OIDC_CLAIM_iat] => 1507893165
[OIDC_CLAIM_iss] => http://192.168.122.235:8080/auth/realms/example
[OIDC_CLAIM_jti] => 1fff1dc8-...5056749451
[OIDC_CLAIM_nbf] => 0
[OIDC_CLAIM_nonce] => XbHe4AZO4Y3mLo1UF1OXfgHiRTuqQ
[OIDC_CLAIM_preferred_username] => ellen.lang
[OIDC_CLAIM_session_state] => cea4da5c-...c106cbb3fefb
[OIDC_CLAIM_sub] => b9c2a39a...b-c84e9382d5bc
[OIDC_CLAIM_typ] => ID
[REDIRECT_OIDC_access_token] => eyJhbGciOiJSUz...8YpL8J4w
[REDIRECT_OIDC_access_token_expires] => 1507893465
[REDIRECT_OIDC_CLAIM_acr] => 0
[REDIRECT_OIDC_CLAIM_aud] => example
[REDIRECT_OIDC_CLAIM_auth_time] => 1507892808
[REDIRECT_OIDC_CLAIM_azp] => example
[REDIRECT_OIDC_CLAIM_email] => info@linuxfabrik.ch
[REDIRECT_OIDC_CLAIM_exp] => 1507893465
[REDIRECT_OIDC_CLAIM_family_name] =>
[REDIRECT_OIDC_CLAIM_given_name] =>
[REDIRECT_OIDC_CLAIM_iat] => 1507893165
[REDIRECT_OIDC_CLAIM_iss] => http://192.168.122.199:8080/auth/realms/example
[REDIRECT_OIDC_CLAIM_jti] => 1fff1dc8-...5056749451
[REDIRECT_OIDC_CLAIM_nbf] => 0
[REDIRECT_OIDC_CLAIM_nonce] => XbHe4AZO4Y3mLo1UF1OXfgHiRTuqQ
[REDIRECT_OIDC_CLAIM_preferred_username] => ellen.lang
[REDIRECT_OIDC_CLAIM_session_state] => cea4da5c-...c106cbb3fefb
[REDIRECT_OIDC_CLAIM_sub] => b9c2a39a...b-c84e9382d5bc
[REDIRECT_OIDC_CLAIM_typ] => ID
[REDIRECT_STATUS] => 200
[REDIRECT_UNIQUE_ID] => WeCgFf0L41OaqnZmBvJ2MQAAAAY
[UNIQUE_ID] => WeCgFf0L41OaqnZmBvJ2MQAAAAY

Caching

Code-Caching mit dem integrirten Zend OPcache.

Abfrage der Statistiken mit php -r 'print_r(json_encode(opcache_get_status(), JSON_PRETTY_PRINT));'. Wichtig: opcache.memory_consumption=200 legt die Gesamtgrösse fest. Wird beispielsweise opcache.interned_strings_buffer auf 100 (MB) gesetzt, stehen nur noch 50% des OpCaches für die maximal opcache.max_accelerated_files compilierten Dateien zur Verfügung.

So funktioniert der Cache anschaulich:

    memory_consumption=200 (MB)
    davon "wasted" max. 5%

┌─────────────────────────────────┐
│                                 │
│ interned_string_buffer=50 (MB)  │
│                                 │
│                                 │
├─────────────────────────────────┤ ------
│                                 │
│ max_accelerated_files=16229 (#) │ Hit Rate
│ ("Keys")                        │
│                                 │
│                                 │
│                                 │
│                                 │
│                                 │
└─────────────────────────────────┘

Data Caches

  • APCu: für lokales Caching

  • Memcached: für verteiltes Caching

  • Redis: lokal, verteilt, File Lock Caching - aber langsamer als APCu

memory_limit vs. post_max_size vs. upload_max_filesize

Angenommen, man möchte Uploads für 2 GB grosse Dateien erlauben. Welche Settings in der php.ini sind wie anzupassen?

PHP verfügt über verschiedene POST-Readers und -Handler, je nach Inhaltstyp der Anfrage. Im Falle von „multipart/form-data“ (was für das Senden von Dateien verwendet wird), agiert der rfc1867_post_handler als mixed Reader/Handler. Er füllt sowohl $_POST als auch $_FILES. Beides zählt in Bezug auf memory_limit. Allerdings enthält $_FILES nur Metadaten über die Dateien, nicht die Dateien selbst. Dateien werden nur auf die Festplatte (auf unter Linux je nach Konfiguration z.B. in /tmp) geschrieben und zählen daher nicht für memory_limit.

Generell gilt also:

  • upload_max_filesize kann beliebig gesetzt werden.

  • post_max_size muss grösser als upload_max_filesize sein, wenn Formulare mit Datei-Uploads unterstützt werden.

  • memory_limit muss grösser als post_max_size sein.

In obigem Beispiel kann also folgendes genügen:

memory_limit = 128M
post_max_size = 8M          ; belongs to sapi_globals
upload_max_filesize = 1G    ; belongs to core_globals

Preloading

Preloading bezeichnet das Verfahren, bei dem PHP-Skripte beim Start des PHP-FPM-Prozesses direkt in den OPCache geladen werden, sodass der Cache von Beginn an warm ist. Ohne Preloading werden Skripte erst beim ersten Aufruf in den Cache aufgenommen, was bei den initialen Anfragen zu einem Mehraufwand führt.

Damit Preloading funktioniert, muss in der php.ini folgendes gesetzt werden:

php.ini
opcache.preload=/var/www/html/preload.php
opcache.preload_user=apache

Das referenzierte PHP-Skript lädt dann die gewünschten Dateien in den OPCache:

/var/www/html/preload.php
<?php
$files = glob('/var/www/html/src/*.php');

foreach ($files as $file) {
    opcache_compile_file($file);
}

Ob Preloading sinnvoll ist, hängt jedoch vom konkreten Anwendungsfall ab. Skripte, die selten oder gar nicht aufgerufen werden, belegen unnötig Arbeitsspeicher, ohne einen messbaren Vorteil zu bringen. Preloading empfiehlt sich daher nur dann, wenn klar definiert ist, welche Skripte regelmässig benötigt werden.

Debugging

Damit man unter PHP-FPM mit „print“-Statements debuggen kann, müssen folgende Einstellungen für den PHP-FPM-Worker gesetzt sein:

/etc/php-fpm.d/www.conf
catch_workers_output = yes
php_admin_value[error_log] = /var/log/php-fpm/www-error.log
php_admin_flag[log_errors] = on

Nun kann so geprintet werden:

error_log("hello world");
error_log(print_r($myArray, true));

// print a stacktrace
error_log(print_r(debug_backtrace(), true));
// with reduced memory usage
error_log(print_r(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), true));