Composer – PHP Package Manager

Der PHP Composer ist ein Package-Management Tool ähnlich dem NPM oder Yarn welcher in der JavaScript-Welt bekannt ist.

Warum braucht man Dependency-Management?

Kaum jemand baut Webseiten komplett von 0 auf – d.h. Usermanagement inkl. Rollen, Medienverwaltung, Login etc.

Also wäre es ja nicht schlecht diese schon implementierten Code-Blöcke in das eigene Tool zu übernehmen, oder?

Jedoch wie bei jeder Software-Entwicklung hängt es immer sehr von der Version ab wie kompatibel 2 Code-Blöcke miteinander sind.

Genau für dieses Problem gibt es den Composer.

Composer weiß durch die Daten in der composer.json bzw. composer.lock welche Module in welcher Version installiert werden sollen und wovon diese Module wieder abhängig sind damit diese auch automatisch installiert werden.

Installation

https://getcomposer.org/download/

Mit diesen 4 Zeilen von PHP wird eine composer.phar in das aktuelle Verzeichniss gelegt.

D.h. über den folgenden Befehl können Composer-Befehle durchgeführt werden:

php composer.phar list

Damit aber Composer „global“ zur Verfügung steht muss ein User mit Admin Rechten diese Datei noch verschieben

mv composer.phar /usr/local/bin/composer

Da jeder Linux und MacOS User den Pfad /usr/local/bin/ in seiner $PATH Variable hat kann somit jeder User Composer Befehle wie folgt ausführen:

composer list

Verwendung

Hier werden ein paar essentielle Funktionalitäten vom Composer beschrieben

composer list

Zeigt alle zur Verfügung stehenden Composer Befehle an

composer create-project <boilerplate-name>

Initialisiert ein Composer-Projekt mit einem gewissen „Boilerplate“ und erzeugt eine composer.json im aktuellen Verzeichnis.

composer require <modulenmae>

Fügt das gewünschte Modul zur composer.json hinzu und führt ein composer update durch.

composer remove <modulenmae>

Entfernt das gewünschte Modul aus der composer.json und aus dem Dateisystem.

composer update

Aktualisiert die aktuell installierten Module mit den neuesten, in der composer.json definierten Updates.
Die Versionen der neu installierten Module werden in der composer.lock gespeichert.

composer install

Wenn eine composer.lock vorhanden ist werden die darin enthaltenen Module in der angegebenen Verion installiert.
Wenn keine composer.lock vorhanden ist werden die aktuellsten Versionen laut composer.json installiert und eine composer.lock generiert.

composer self-update

Aktualisiert die installierte Composer Version

composer outdated --direct

Zeigt alle Module an, die aktualisiert werden können.
Das –direct heist, dass nur Modul-Updates angezeigt werden, die in der composer.json definiert sind und nicht weitere Dependencies dahinter.


Aufbau der composer.json

Die simpelste Version einer composer.json kann über den Befehl composer init generiert werden:

{
    "name": "devguide/myapp",
    "authors": [
        {
            "name": "Kevin Pfeifer",
            "email": "info@devguide.at"
        }
    ],
    "require": {}
}

Prinzipiell steht hier nur wie der Name des Modul ist devguide/myapp, wer der Author des Moduls ist und ob es Dependencies zu diesem Modul gibt.

require vs. require-dev

Wie üblich in der Software-Entwicklung gibt es häufig eine Produktionsumgebung und eine Testumgebung

Damit Module in der Produktionsumgebung nicht installiert werden müssen einerseits diese in dem require-dev Bereich stehen, andererseits müssen die Module in der Produktionsumgebung auch mit der folgenden Option installiert werden:

composer install --no-dev

Somit bleiben alle Development-Module, die das Leben für uns Entwickler vereinfachen, nur auf der Testumgebung und nehmen keinen Platz auf der Live-Seite ein.

Schreibweise für die Versionen

NameKurzschreibweiseVersion Range
Exact Version1.0.21.0.2
Version Range>=1.0 <2.0>=1.0 <2.0
>=1.0 <1.1 || >=1.2>=1.0 <1.1 || >=1.2
Hyphenated Version Range1.0 – 2.0>=1.0.0 <2.1
1.0.0 – 2.1.0>=1.0.0 <=2.1.0
Wildcard Version Range1.0.*>=1.0 <1.1
Tile Version Range~1.2>=1.2 <2.0
~1.2.3>=1.2.3 <1.3.0
Caret Version Range^1.2.3>=1.2.3 <2.0.0

Composer Patches

Wie überall bei der Softwareentwicklung funktionieren nicht immer die aktuellen Versionen zu 100% wie gewünscht.

Bevor aber eine neue Version vom Composer Modul Hersteller veröffentlicht wird gibt es meistens Patches, die angewewendet werden können.

Diese sind ganz normale .patch Files, die über GIT erstellt werden können.

Beispiel: https://www.drupal.org/files/issues/2019-02-07/3030251—​entity-owner-trait—​11.patch

diff --git a/consumers.install b/consumers.install
index 3ca8e25..22287bc 100644
--- a/consumers.install
+++ b/consumers.install
@@ -8,7 +8,7 @@
 use Drupal\consumers\Entity\Consumer;
 use Drupal\Core\Field\BaseFieldDefinition;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
-
+use Drupal\user\Entity\User;
 /**
  * Implements hook_install().
  */
@@ -161,3 +161,20 @@ function consumers_update_8105() {
     'is_default' => TRUE,
   ])->save();
 }
+
+/**
+ * 'owner_id' field should be not null.
+ */
+function consumers_update_8106() {
+  // Set owner_id to AnonymousUser id when null.
+  $anonymous_user = User::getAnonymousUser();
+
+  \Drupal::database()->update('consumer_field_data')
+    ->fields(['owner_id' => $anonymous_user->id()])
+    ->isNull('owner_id')
+    ->execute();
+
+  $entity_definition_update_manager = \Drupal::entityDefinitionUpdateManager();
+  $field_definition = $entity_definition_update_manager->getFieldStorageDefinition('owner_id', 'consumer');
+  $entity_definition_update_manager->updateFieldStorageDefinition($field_definition);
+}

Voraussetzung

Um Patches mit dem Composer automatisiert anzuwenden müssen folgende Voraussetzungen erfüllt werden:

  1. „cweagans/composer-patches“ muss required werden
  2. Patching von Dependencies erlauben
{
  "require": {
      "cweagans/composer-patches": "^1.6.0"
  },
  "extra": {
      "enable-patching": true
  }
}

Patch in composer.json hinzufügen

"patches": {
    "drupal/<module_or_theme>": {
        "<Description>": "<URL-to-patch>"
    }
},

Also z.B.

"patches": {
    "drupal/recaptcha": {
        "CAPTCHA validation error: unknown CAPTCHA session ID (issues/3035883)": "https://www.drupal.org/files/issues/2019-11-15/3035883-29-workaround.patch"
    }
},

Hier wird also das Modul drupal/recaptcha gepatched werden.

Wenn composer install oder composer update durchgeführt wird, sollte der Patch wie folgt angezeigt werden:

Probleme beim Patching

Wie hier zu sehen kann der Patch nicht heruntergeladen werden, da er unter der URL nicht erreichbar ist.

Hier bitte einfach die URL überprüfen ob diese auch wirklich 100% korrekt ist.

Hier ist zu sehen, dass der Patch nicht anwendbar ist.

Dies hat meistens den Grund, dass das installierte Modul den angegebenen Patch schon inkludiert hat.

Hier würde ich empfehlen den Changelog des Moduls durchzusehen ob der angegebene Patch nicht schon in einer neuen Version angewandt wurde. Dann braucht man natürlich den Patch nicht mehr händisch durchführen.

Probleme beim Update Prozess

Hier könnte es diverse Probleme geben

Berechtigungsproblem

PHP Memory Limit Problem

Hier kommt es natürlich immer sehr auf die Fehlermeldung an, die gerade angezeigt wird.

Das Problem mit dem Memory Limit muss über die php.ini gelöst werden. D.h. über

php -i | grep 'Loaded Configuration File'

Diese gibt bei mir am MacOS folgenden Output:

Loaded Configuration File => /etc/php.ini

und am Linux

Loaded Configuration File => /etc/php/7.3/cli/php.ini

In dieser Datei sollte der folgende Eintrag auf mind. 3G erhöht werden (zu mindest für Drupal Projekte)

memory_limit = 3G

Berechtigunngsprobleme müssen immer je nach aktueller Umgebung gelöst werden.

Während des Update Vorgangs ist die Seite NICHT bedienbar bzw. wird Fehlermeldung anzeigen. Hier wird prinzipiell empfohlen den Wartungsmodus einzuschalten oder den gesamten Webserver kurzzeitig zu deaktivieren.

Installierte Module entsprechen nicht der composer.json bzw composer.lock

Manchmal kann es vorkommen, dass beim installieren bzw. aktualisieren von Composer Modules (bzw. Drupal Modules) nicht die richtige Version installiert wird bzw. eine defekte Version installiert.

Die einfachste Lösung hier ist es alle automatisch generierten Dateien und Ordner zu löschen und über den Composer neu zu installieren. Also:

rm -rf vendor/*
composer install

Bzw. für Drupal 8 Projekte

rm -rf vendor/* docroot/modules/contrib/*
composer install

Wo finde ich Composer Packages?

Alle aktuell vorhandenen Packages können hier durchsucht werden: https://packagist.org/

Was ist PHP?

PHP (Abkürzung für Hypertext Preprocessor) ist eine Open-Source Skriptsprache, welche hauptsächlich in der Web-Entwicklung zur Generierung von HTML-Code verwendet wird.

Beispiel:

<?php
   $variable = "Testvariable";
   echo "Folgende Variable wurde definiert: " . $variable;
?>

Generiert folgendes Ergebnis:

Folgende Variable wurde definiert: Testvariable

In diesem Beispiel wird eine Variable definiert und über den Befehl „echo“ an den Text „Folgende Variable wurde definiert:“ hinten hinzugefügt und ausgegeben.

Da PHP eine Skriptsprache ist wird bei jedem Aufruf der PHP Datei der Inhalt neu interpretiert. Im Gegensatz dazu wird am Beispiel von Java oder C erst der Source-Code durch einen „Compiler“ in Maschinen-Code umgewandelt bevor er ausgeführt werden kann.

Standardmäßig bietet PHP schon einige unterschiedlichste Funktionalitäten zur Verfügung – wie z.B.:

  • Datenbank-Zugriff (MySQL, PostgreSQL, SQLite etc.)
  • Dateisystem-Zugriff (Ordner und Dateien erstellen/bearbeiten/löschen etc.)
  • String-Abänderungen (Texte erstellen/bearbeiten/formatieren etc.)
  • XML-Verarbeitung (Datenstrukturen erstellen/bearbeiten/durchsuchen etc.)
  • u.v.m.

Dies sind nur ein paar Beispiele an Funktionalitäten die in der „Standard-PHP“ Installation integriert sind. Jedoch gibt es „PHP-Module“ um die Funktionalität von PHP noch zu erweitern.

Bekannte bzw. häufig verwendete Beispiel hierfür wären:

  • XDebug (Erweiterte Debug Funktionalität um Probleme im PHP-Code schneller zu finden)
  • OPCache (Speichert vorkompilierte Bytecodes aus PHP-Code im Arbeitsspeicher anstatt diesen bei jedem Aufruf neu zu generieren => Performance-Boost)
  • MBString (Ermöglicht das richtige Handhaben von „MultiByte Strings“ in PHP – z.B. von Emoji Icons)
  • GD (Bildbearbeitung – Bilder drehen/umwandeln etc.)
  • SOAP (Eine spezielle Variante von XML Datenstrukturen)
  • u.v.m.

Wie PHP implementiert bzw. aufgerufen wird kann in dem Beitrag „CLI vs WebServer Integration“ nachgelesen werden.

Da sich mit jeder PHP Version aktuelle Funktionen ändern, entfernt werden und neue hinzugefügt werden wurden die wichtigsten Änderungen von PHP 7.1, 7.2 und 7.3 HIER zusammengefasst.

Sources:
PHP: Was ist PHP – https://www.php.net/manual/de/intro-whatis.php

PHP-Versionen

Wie überall in der Software-Entwicklung gibt es auch hier Versionen, die mit der Zeit die „alten“ Funktionalitäten verbessern bzw. neue Funktionalität hinzufügen. Die folgende Liste zeigt einen kurzen Überblick über die wichtigsten Änderungen von PHP 7.1 bis 8.1.

PHP 5.6 und 7.0 wurden hier nicht mehr eingebunden, da diese laut php.net nicht mehr mit Security-Patches versorgt werden und damit auch nicht mehr aktiv verwendet werden sollten. (Stand April 2019)

PHP 8.1

Nativer ENUM Support

Es ist nun möglich (ähnlich wie in anderen modernen Programmiersprachen wie z.b. Kotlin, Rust und Swift) sogenannte ENUMs (kurz für enumerated types) zu erstellen.

Im simpelsten Fall hätten wir z.b. folgendes ENUM

enum TransportMode {
  case Bicycle;
  case Car;
  case Ship;
  case Plane;
  case Feet;
}

und können es wie folgt verwenden:

function travelCost(TransportMode $mode, int $distance): int
{ /* implementation */ } 

$mode = TransportMode::Boat;

$bikeCost = travelCost(TransportMode::Bicycle, 90);
$boatCost = travelCost($mode, 90);

// this one would fail: (Enums are singletons, not scalars)
$failCost = travelCost('Car', 90);

Aber es gibt auch die Möglichkeit den unterschiedlichen ENUM Cases einen skalaren Wert zuzuteilen:

enum Metal: int {
  case Gold = 1932;
  case Silver = 1049;
  case Lead = 1134;
  case Uranium = 1905;
  case Copper = 894;
}

Hier gibt es aber einige Regeln zu beachten:

  • Wenn ein case einen skalaren Wert zugewiesen bekommt müssen ALLE cases einen skalaren Wert erhalten.
  • Skalare Werte müssen (gleich wie der Case selbst) eindeutig sein.
  • Die Skalaren Werte sind read only
  • Um auf den Wert eines ENUM Cases zuzugreifen muss dies über Metal::Gold->value durchgeführt werden

Der never Return Type

Bis jetzt konnte man nie einer Funktion vorgeben, dass sie nichts returnen darf. Es gibt nur den void Return Type dem es aber egal ist ob ein exit ausgeführt wurde oder nicht.

Dies kann nun wie folgt erzwungen werden:

function shutdown(): never {
    exit();
}

Dies kann aber natürlich auch durch einen impliziten Funktionsaufruf passieren wie z.b.:

function redirectToHome(): never {
    redirect('/');
}

Fibers (Non-Blocking/Asynchrones PHP)

Für alle die nicht in die Welt von z.b. NodeJS oder anderen asynchronen Programmiersprachen eingestiegen sind wird dieses Thema etwas eigenartig sein. Prinzipiell wird in PHP der Code synchron ausgeführt. Dies bedeutet, dass der Code in Zeile 2 erst ausgeführt wird wenn der Code in Zeile 1 fertig durchgeführt wurde.

Mit PHP 8.1 wurden Fibers eingeführt um genau dies nun zu ändern und asynchrones PHP zu ermöglichen.

Mit externen Packages wie z.b. amphpReactPHP und Guzzle konnte dies schon in der Vergangenheit erreicht werden jedoch gab es (bis jetzt) keinen definierten Standard dafür.

Für ein gutes Beispiel siehe bitte HIER

Readonly Properties

Es ist nun möglich eine property als readonly zu definieren sodass sie nur 1 mal initialisiert werden darf und danach nicht noch einmal geändert werden darf.

class Release {
    public readonly string $version;
 
    public function __construct(string $version) {
        // Legal initialization.
        $this->version = $version;
    }
}

Was ist nun aber der Unterschied zu Properties mit const?

public readonly string $version; bedeutet, dass unterschiedliche Objekte vom Typ Release unterschiedlich (in sich nicht veränderbare) Werte haben dürfen.

Hingegenpublic const string $version; bedeutet, dass alle Objekte vom Typ Release den gleichen Wert haben müssen.

PHP 8.0

JIT (Just in Time Compiler)

Ohne zu weit ins Detail zu gehen wird mit PHP 8.0 ein JIT Compiler eingeführt der „unter speziellen Bedingungen“ eine bessere Performance bringt. Diese sind aber anscheinend nicht typische CMS wie z.b. WordPress sondern mehr „numerische“ Logik verbessert.

Bild: https://www.php.net/releases/8.0/en.php

Constructor Property Promotion

Bis jetzt mussten Properties beim initialisieren eines Objects wie folgt umgesetzt werden:

class Car {
    public int $wheels;
    public int $doors;

    public function __construct(
        int $wheels = 4,
        int $doors = 5
    ) {
        $this->wheels = $x;
        $this->doors = $y;
    }
}

Dies kann nun aber wesentlich kompakter geschrieben werden:

class Carr {
    public function __construct(
        public int $wheels = 4,
        public int $doors = 5
    ) {}
}

Union Types

Bis jetzt gab es prinzipiell nur den ?Type Union Type um Parameter, Properties oder Return-Types sowohl einen Type zuzuweisen als auch null zu erlauben.

Ansonsten konnten nur über PHPDoc Union Types definiert werden welches wie folgt aussieht


/**
 * @property int|float $power
 */
class Car {
    private $power;

    /**
     * @param int|float $power
     */
    public function setPower($power) {
        $this->power = $power;
    }

    /**
     * @return int|float
     */
    public function getPower() {
        return $this->power;
    }
}

Dies wurde nun in der Hinsicht erweitert, dass mehrere unterschiedliche Typen bei z.b. Properties, Parameters und Return-Types definiert werden dürfen.

class Number {
    private int|float $number;

    public function setNumber(int|float $number): void {
        $this->number = $number;
    }

    public function getNumber(): int|float {
        return $this->number;
    }
}

Prinzipiell wurde bei PHP 8.0 generell mehr auf Typisierung geachtet bzw. werden Typisierungen weiter verstärkt.

Named Attributes

Bis jetzt bestimmte die Position der Parameter innerhalb der Funktionsdefinition wie diese Parameter beim Aufruf angeordnet sein müssen.

function calc($start, $end, $steps) {
    ...
}

Und kann damit nur wie folgt aufgerufen werden:

calc(10, 100, 5);

Nun ist es aber möglich die Funktion wie folgt zu aufzurufen

calc(end: 100, start: 10, steps: 5);

Man darf es aber auch kombinieren um z.b. folgenden Aufruf zu machen:

calc(10, steps: 5, end: 100);

Hier muss aber die Position der nicht benannten Argument korrekt sein!

Nullsafe-Operator

Manchmal möchte man auf ein Object eine Funktion nur dann aufrufen wenn dieses Object auch wirklich vorhanden ist.

$result = null;
if($a !== null) {
    $result = $a->b();
}

Dies kann nun aber auf folgenden Code verkürzt werden:

$result = $a?->b();

Wenn $a null ist, wird die Methode b() nicht aufgerufen und $result wird auf null gesetzt.

Neue String-Vergleich Funktionen

Ich muss regelmäßig String-Operations durchführen um z.b. zu testen ob ein String einen gewissen anderen String beinhaltet, mit diesem startet etc.

Dies konnte bis jetzt nur über die strpos() Funktion durchgeführt werden und musste abhängig von dessen Return Value weitere Logik erst implementiert werden.

Mit PHP 8.0 wurden folgende selbsterklärenden Funktionen eingeführt

  • str_contains( $haystack, $needle )
  • str_starts_with( $haystack, $needle )
  • str_ends_with( $haystack, $needle )

Damit ist es nun wesentlich leichter besser lesbaren Code zu schreiben.

PHP 7.4

Spread-Operator für Arrays

Bei Funktionsaufrufen war der Spread-Operator schon bekannt.

function my_function(...$args) { var_dump($args); }
my_function('test', true, 123);

// Resultat
array(3) {
  [0]=>
  string(4) "test"
  [1]=>
  bool(true)
  [2]=>
  int(123)
}

Dies ist nun auch für Array-Operationen möglich.

$bool_arr = [true, false];
$total_arr = ['text1', 123, ...$bool_arr , 456];
var_dump($total_arr );

// Resultat
array(5) {
  [0]=>
  string(5) "text1"
  [1]=>
  int(123)
  [2]=>
  bool(true)
  [3]=>
  bool(false)
  [4]=>
  int(456)
}

Diese Art von Array Zusammenführung soll ebenso performanter sein als die übliche array_merge() Funktion.

Arrow Funktionen

Ein typischer Fall für Arrow Funktionen wäre z.b. folgendes Beispiel

function cube($n){
	return ($n * $n * $n);
}
$a = [1, 2, 3, 4, 5];
$b = array_map('cube', $a);

Wird zu

$a = [1, 2, 3, 4, 5];
$b = array_map(fn($n) => $n * $n * $n, $a);

Null Coalescing Assignment Operator

Mit PHP 7.0 ist es ja schon möglich einen Fallback Wert zu setzen wenn eine gewissen Variable nicht gesetzt ist.

$this->request->data['comments']['user_id'] = $this->request->data['comments']['user_id'] ?? 'value';

Diese Schreibweise kann nun wie folgt vereinfacht werden

$this->request->data['comments']['user_id'] ??= 'value';

Typisierte Klassen-Argumente

Es ist nun möglich folgende Klassen-Definition

class User {
    /** @var int $id */
    private $id;
    /** @var string $name */
    private $name;
 
    public function __construct(int $id, string $name) {
        $this->id = $id;
        $this->name = $name;
    }
}

wie folgt zu vereinfachen

class User {
    public int $id;
    public string $name;
}

Folgende Typen sind hier erlaubt:

  • bool
  • int
  • float
  • string
  • array
  • object
  • iterable
  • self
  • parent
  • Jegliche/s Klasse oder Interface
  • Nullable Typen (?type)

Jedoch dürfen keine void als auch callable Typen definiert werden!

Preloading

Ohne in komplizierte Details zu gehen bewirkt das OPCache preloading, dass Libraries in den OPCache vorgeladen und damit immer zur Verfügung stehen statt sie bei jedem PHP-Prozess neu zu laden. Dies bewirkt einen deutlichen Performance-Gewinn.

Diese Einstellung muss in der php.ini gesetzt werden.

PHP 7.3

Nachgestelltes Komma in Funktions-Parametern erlaubt

Ab PHP 7.3. ist es nun erlaubt beim letzten Parameter eines Funktionsaufrufs ein Komma hinten anzustellen.

my_function(
    $param1,
    $param2,
);

JSON_THROW_ON_ERROR

Aktuell gibt json_decode() null bei einem Fehler zurück.
null kann aber auch ein gültiges Ergebnis sein was zu Verwirrungen führen kann.

Nun gibt es folgende Funktionen:

  • json_last_error()
    • Gibt (sofern vorhanden) den letzten Fehler zurück, der beim letzten Kodieren/Dekodieren von JSON aufgetreten ist.
  • json_last_error_msg()
    • Gibt bei Erfolg die Fehlermeldung zurück, „No error“, wenn kein Fehler aufgetreten ist. Im Fehlerfall wird FALSE zurückgegeben.

Ein anonymer User hat bei der php.net Definition der json_last_error_msg() Funktion eine nette Helper-Funktion geschrieben, welche die Fehler-Ausgabe bei json_decode() vereinfacht:

<?php
    if (!function_exists('json_last_error_msg')) {
        function json_last_error_msg() {
            static $ERRORS = array(
                JSON_ERROR_NONE => 'No error',
                JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
                JSON_ERROR_STATE_MISMATCH => 'State mismatch (invalid or malformed JSON)',
                JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded',
                JSON_ERROR_SYNTAX => 'Syntax error',
                JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded'
            );

            $error = json_last_error();
            return isset($ERRORS[$error]) ? $ERRORS[$error] : 'Unknown error';
        }
    }
?>

Alternativ kann dies auch über einen try-catch Block gelöst werden:

try {
    json_decode("{", false, 512, JSON_THROW_ON_ERROR);
}
catch (\JsonException $exception) {
    echo $exception->getMessage(); // echoes "Syntax error"
}

is_countable Funktion

Aktuell ist es für z.b. foreach-Schleifen üblich die Variablen wie folgt zu überprüfen:

$array = [1,2,3,4,5,6,7,8];

if(is_array($array) && sizeof($array) > 0){
  foreach($array as $value){
    
  }
}

Dies kann nun wie folgt verkürzt werden:

$array = [1,2,3,4,5,6,7,8];

if(is_countable($array)){
  foreach($array as $value){
    
  }
}

array_key_first(), array_key_last()

Bis jetzt war es nicht „einfach“ möglich den ersten bzw. letzten „key“ eines Arrays zu erhalten. Mit den Funktionenarray_key_first()und array_key_last() ist dies nun einfach möglich.

PHP 7.2

Native Untersützung für das Bild-Format (BMP)

Bis jetzt musste man bei der Bild-Bearbeitung von BMP-Bildern über PHP externe Bibliotheken verwenden. Jedoch bietet seit PHP 7.2 die GD-Bibliothek die Unterstützung für dieses Bildformat mit an.

Typ „object“ bei Parameter- und Rückgabewerten einstellbar

Bis jetzt war es nicht bei Funktionen als Paramter- oder Rückgabewerte eine Variable vom Typ „object“ anzugeben. Seit PHP 7.2 ist dies nun aber möglich.

function test (object $a): object {

}

exif_read_data() erkennt mehr Daten aus Bildern

EXIF (Exchangeable Image File Format) ist ein Standard für das Abspeichern von Metadaten bei Bild-Dateien.

Bis jetzt waren diese automatisch ausgelesenen Daten aus PHP sehr beschränkt. Jedoch wurden mit PHP 7.2 diverse EXIF-Formate von bekannten Kamera-Herstellern hinzugefügt. Siehe HIER

Verschlüsselte ZIP-Archive nun möglich

Ab PHP 7.2 ist es nun möglich ZIP-Archive mit Passwörtern zu erstellen.

PHP 7.1

„Nullable types“

function test1(?array $a) {
 
}
 
function test2($a): ?array {
 
}

Die erste Funktion (test1) besagt, dass ein Parameter vom Typ „Array“ der Funktion übergeben werden kann, dieser jedoch auch „null“ sein kann (? vor dem array)

Die zweite Funktion (test2) besagt, dass der Rückgabewert der Funktion vom Typ „Array“ sein kann, jedoch aber auch „null“ erlaubt ist.

Ohne diese vorangestellten ? würde bei einem Aufruf der Funktion test1 mit dem Paramter „null“ bzw. bei der Funktion test2 mit dem Rückgabe-Wert „null“ ein „Fatal Error“ produziert werden.

Array und list haben gleiche Funktionalität

Alter Code:

$array = array(0 => 'a', 1 => 'b');
 
list($a, $b) = $array;
 
// $a ist = 'a'
// $b ist = 'b'

Neuer Code:

$array = array(0 => 'a', 1 => 'b', 2 => 'c');
 
[$a, $b] = $array;
 
// $a ist = 'a'
// $b ist = 'b'
 

list(1 => $b, 2 => $c) = $array;
// ist das gleiche wie
[1 => $b, 2 => $c] = $array;

Sichtbarkeiten von Konstanten in Klassen

Wie es schon bei Klassen-Variablen und Funktionen bekannt ist können nun „Sichtbarkeiten“ (bekannt aus der Objektorientierten Programmierung) auch auf Konstanten angewandt werden.

  • public Zugriff auf Variablen ist von überall möglich, d.h. auch von anderen Klassen bzw. Objekten
  • protected Zugriff auf Variablen ist nur in der eigenen Klasse UND von allen Klassen, die von der eigenen Klasse abgeleitet (=extended) wurden, möglich.
  • private Zugriff auf Variablen ist nur in der eigenen Klasse vorhanden.
class test {
    const PUBLIC1 = 'TEST';
 
    public const PUBLIC2 = 'TEST';
    protected const PROTECTED = 'TEST';
    private const PRIVATE = 'TEST';
}

try-catch mit mehreren Exception Angaben möglich

Es ist nun möglich in einem try-catch Block mehrere Exception-Typen mit einem | anzugeben.

try {
     throw new Exception('Fehler');
} catch (Exception | AndererExceptionTyp $catchedException) {
     var_dump($catchedException);
}

mcrypt Erweiterung veraltet => OpenSSL verwenden

Die Funktionen der „mcrypt“ Erweiterung (Funktionsnamen die mit „mcrypt_“ starten) werden nun als „deprecated“ markiert bzw. bei Verwendung in der error.log als „deprecated“ angezeigt.

Ersetzt wird die mcrypt Funktionlität mit den Funktionen aus der OpenSSL Erweiterung.

Sources

mod_php vs (Fast)CGI vs FPM

Es gibt mehrere Arten wie PHP Dateien vom Web Server verarbeitet werden können. Die folgenden Bereiche beschreiben die bekanntesten Implementierungen:

mod_php

„mod_php“ ist ein Module für den Web-Server „apache“.

Mit diesem Modul ist PHP sozusagen „integriertim Web-Server. Das bewirkt, dass es keinen extra PHP-Prozess zur Abwicklung des PHP-Codes gibt sondern alles vom Apache-Prozess verarbeitet wird.

Der große Vorteil bei der Verwendung von „mod_php“ ist Performance. Im Vergleich zu CGI bewirkt der Umstieg auf „mod_php“ einen durchschnittlichen Performance-Gewinn von 300-500%.

Grund dafür ist die Möglichkeit des Cachings von diversen PHP-Modulen bzw. Konfigurationen die sonst (bei CGI und FastCGI) bei jedem Aufruf einer Seite immer neu ausgelesen und abgearbeitet werden müssen.

Wie erkenne ich, ob mod_php verwendet wird?

Nachdem mod_php ein Web-Server Modul ist muss es in der Webserver-Config wie folgt geladen werden (hier am Beispiel von Apache2)

LoadModule php7_module modules/mod_php.so

Auf meinem Ubuntu Server war z.b. folgender Eintrag in der /etc/apache2/mods-available/php7.4.load vorhanden

LoadModule php7_module /usr/lib/apache2/modules/libphp7.4.so

Dies führt dann typischerweise dazu, dass in der phpinfo() Ausgabe wie folgt darauf hingewiesen wird:

Server APIApache 2.0 Handler
Loaded Modulescore mod_so mod_watchdog http_core mod_log_config mod_logio mod_version mod_unixd mod_access_compat mod_alias mod_auth_basic mod_authn_core mod_authn_file mod_authz_core mod_authz_host mod_authz_user mod_autoindex mod_deflate mod_dir mod_env mod_filter mod_mime prefork mod_negotiation mod_php7 mod_reqtimeout mod_setenvif mod_status

CGI

Die „Common Gateway Interface“ (kurz CGI) Implementation bedeutet, dass der Web-Server pro Anfrage eine neue PHP-Interpreter-Prozess startet. Dadurch müssen bei jeder Anfrage alle PHP-Module, die php.ini und die diversen anderen Konfigurationen neu geladen und durchgeführt werden, was sehr ineffizient ist.

Hauptvorteil von der CGI Implementation ist die komplette Isolierung zwischen ausgeführten Web-Server Code und PHP-Code.

FastCGI

FastCGI ist eine PHP-Implementation, die die Security-Vorteile von CGI implementiert aber trotzdem effizient in der Abarbeitung ist wie mod_php.

Hier wird nicht pro Request eine neue PHP-Interpreter-Instanz gestartet (was das primäre Problem war bei CGI), sondern es gibt schon „fertige“ PHP-Interpreter-Instanzen, denen nur die angefragte PHP-Datei übergeben wird.

Wie erkenne ich, ob FastCGI verwendet wird?

Serverseitig gibt es hier mehrere Möglichkeiten wie dies umgesetzt werden kann. Ein Beispiel wäre ein lokal verfügbares, ausführbares PHP Binary:

ScriptAlias /myPHP /home/devguide/php-install-directory
AddHandler cgi-php .php
Action cgi-php /myPHP/php-cgi

Hier ist also /home/devguide/php-install-directory/php-cgi ein spezielles PHP Binary für CGI Zwecke ist (sollte beim installieren von PHP automatisch mitkommen sollte oder als eigenes Package installiert werden kann).

In derphpinfo() Ausgabe sieht das ganze wie folgt aus:

Server APICGI/FastCGI

FPM

Der „PHP-FastCGI Process Manager“ (kurz PHP-FPM) ist eine alternative zur FastCGI Implementation von PHP in einem Webserver.

Hier besteht immer parallel zum Web-Server Prozess (zu mindest) ein PHP-FPM Prozess, an den PHP-Anfragen vom Web-Server weitergeleitet werden.

Für weitere Details zu FPM siehe HIER

Source: https://blog.layershift.com/which-php-mode-apache-vs-cgi-vs-fastcgi/

Wie erkenne ich, ob FPM verwendet wird?

FPM verwendet einen eigenen Service mit dem die FPM-Pools verwaltet werden. D.h. wenn man die Services des Servers sehen kann (z.b. über top oder htop) wird man recht schnell herausfinden, ob PHP-FPM verfügbar ist bzw. verwendet wird.

In derphpinfo() Ausgabe sieht das ganze wie folgt aus:

Server APIFPM/FastCGI
php-fpmactive

PHP-FPM

Der „PHP-FastCGI Process Manager“ (kurz PHP-FPM) ist eine alternative zur FastCGI Implementation von PHP in einem Webserver.

Hier besteht immer parallel zum Web-Server Prozess (zu mindest) ein PHP-FPM Prozess, an den PHP-Anfragen vom Web-Server weitergeleitet werden.

FPM-Prozesse unterteilen sich in unterschiedliche „Pools“. In diesen Pools werden typischerweise mehrere Prozesse für eine gewissen Seite gestartet werden damit diese bei Anfragen über den Web-Server gleich zur Verfügung stehen.

Die Anzahl dieser Prozesse und diverse anderen Einstellungen für den Pool können in der FPM Konfiguration (wie weiter unten zu sehen) eingestellt werden.

Installation (Debian basierte Distros)

sudo apt install php7.2 php7.2-common php7.2-cli php7.2-fpm

FPM Konfiguration

Diese befinden sich unter „/etc/php/7.2/fpm/pool.d/“.
Hier wird pro Socket eine Datei erstellt.

[<pool-name>]
user = <username>
group = <groupname>
listen = /run/php/<socket-name>.sock
listen.owner = www-data
listen.group = www-data
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
env[HOSTNAME] = $HOSTNAME
env[PATH] = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
chroot = <docroot-path>
php_admin_value[openssl.capath] = /etc/ssl/certs

Beim eingetragenen Pfad bei „listen“ wird nach neustarten des PHP-FPM-Prozesses ein Socket erstellt.

Über diesen kann dann der jeweilige Web-Server die Anfragen weiterleiten (siehe unten)

Web-Server Konfiguration (NGINX)

Der Web-Server muss dafür so konfiguriert werden, dass PHP-Files an den jeweiligen PHP-Prozess (typischerweise über einen Socket) weitergeleitet werden.

Am Beispiel vom NGINX-Web-Server: /etc/nginx/sites-available/<domain-name>.conf

server {
    listen 443 ssl http2; # managed by Certbot
    listen [::]:443 ssl http2;

    ssl_certificate /etc/letsencrypt/live/<domain-name>/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/<domain-name>/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

    root <path-to-docroot>;
    server_name <domain-name>;

    index index.html index.htm index.php;

    location / {
        try_files $uri $uri/ /index.php?$args;
    }	

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
	fastcgi_pass unix:/run/php/<fpm-socket-name>.sock;
    }
	
    error_log <path-to-logs>/error.log warn;
    access_log <path-to-logs>/access.log apm;
}

server {
    if ($host = <domain-name>) {
        return 301 https://$host$request_uri;
    } # managed by Certbot
	
    listen 80;
    listen [::]:80;

    server_name <domain-name>;
    return 404; # managed by Certbot
}

Die folgende Zeile ist für die „Weiterleitung“ an den FPM-Prozess verantwortlich

fastcgi_pass unix:/run/php/<fpm-socket-name>.sock;

Der PHP-FPM Prozess muss bei jeder neu erstellten oder geändert Konfiguration neu gestartet werden um den Socket zu erstellen, zu dem sich dann der Webserver verbindet. In dem gleichen Zug kann der Webserver auch gleich neugestartet werden.

sudo systemctl restart php7.2-fpm.service nginx

CLI vs WebServer Integration

Wie in der PHP-FPM Beschreibung schon beschrieben können unterschiedliche Konfigurationen für unterschiedliche vHosts erstellt werden.

Das hat natürlich den Vorteil, dass unterschiedliche vHosts mit unterschiedlichen PHP Versionen ausgeführt werden können, was bei Legacy-Software öfters der Fall ist, da diese die neuesten PHP Standards nicht implementiert haben und zu mindest Warnings auftauchen.

Diese Konfiguration ändert aber nichts an der systemweiten installierten PHP Version.

Das bedeutet, dass die PHP Version aus einer PHP-Info Datei nicht unbedingt die gleiche sein muss wie die PHP Version, die aus dem folgenden Befehl über das Terminal erscheint.

php -v
PHP 7.2.19-1+ubuntu18.04.1+deb.sury.org+1 (cli) (built: May 31 2019 11:17:15) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.2.19-1+ubuntu18.04.1+deb.sury.org+1, Copyright (c) 1999-2018, by Zend Technologies
    with Xdebug v2.7.1, Copyright (c) 2002-2019, by Derick Rethans

Wie ändert man die Standard PHP Version in der CLI?

HINWEIS: Dies ist nur möglich, wenn root bzw. sudo Rechte am Server vorhanden sind!

Über folgenden Befehl findet man heraus, was der Pfad zu einem Befehl ist

which php

Dieser gibt bei meinem Server aktuell folgendes aus:

/usr/bin/php

Wenn man nun diese „Datei“ genauer betrachtet sieht man folgendes:

ls -al /usr/bin/php        
lrwxrwxrwx 1 root root 21 Jan  3  2018 /usr/bin/php -> /etc/alternatives/php

Das bedeutet, dass /usr/bin/php eigentlich nur auf /etc/alternatives/php zeigt, also eine Art Verknüpfung. Hier sieht man aber schon am Pfad, dass es etwas mit den „alternatives“ zu tun hat.

Wenn diese Datei genauer betrachtet wird sieht man folgendes:

ls -al /etc/alternatives/php        
lrwxrwxrwx 1 root root 15 May 31  2018 /etc/alternatives/php -> /usr/bin/php7.2

Dieser Link zeigt nun zum „wirklichen“ Binary, welches dann PHP in der Version ausführt.

ls -al /usr/bin/php7.2      
-rwxr-xr-x 1 root root 4899864 May 31 13:17 /usr/bin/php7.2

D.h. der Befehl „php“ liegt in „/usr/bin/php“, welcher dann weiter zu „/etc/alternatives/php“ und final zu „/usr/bin/php7.2“ linkt.

Um diese „Verlinkung“ nun anzupassen (um eine andere PHP Version in der CLI zu erhalten) kann folgender Befehl eingegeben werden:

sudo update-alternatives --set php /usr/bin/php7.3

Damit haben wir die Standard PHP Version in der CLI von 7.2 auf 7.3 geändert.

php -v
PHP 7.3.6-1+ubuntu18.04.1+deb.sury.org+1 (cli) (built: May 31 2019 11:06:48) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.6, Copyright (c) 1998-2018 Zend Technologies
    with Zend OPcache v7.3.6-1+ubuntu18.04.1+deb.sury.org+1, Copyright (c) 1999-2018, by Zend Technologies
    with Xdebug v2.7.1, Copyright (c) 2002-2019, by Derick Rethans