Alvine PHP-Framework

Das Alvine PHP-Framework ist grundlegend so ausgelegt, das unterschiedliche Speichermöglichkeiten für die verschiedenen Einsatzmöglichkeiten verwendet werden können und sollen. So gibt es für Session-Daten mit einer geringen Haltbarkeit andere Anforderungen als für Buchhaltungsdaten.

Unterschiedliche Anforderungen

Für ein Unternehmen ist die Datenhaltung eines der entscheidenden Faktoren, da die Daten oft deutlich länger leben müssen, als die verwendeten Programme. So muss man in Deutschland sicherstellen, das der Zugriff auf bestimmte steuer relevante Dokumente auch noch in zehn Jahren gewährleistet werden kann. Für Sessiondaten eines Benutzers hingehend muss nur eine begrenzte Lebensdauer gewährleistet werden. Allerdings sollte der Zugriff auf diese Daten - um einen schnellen Zugriff auch bei hoher Last zu gewährleisten - möglichst einfach skalierbar sein. In der nachfolgenden Tabelle sind die einzelnen Produkte den Anforderungen zugeordnet.

Bei der Entwicklung einer Alvine2 Anwendung muss man für seinen Daten eine Optimale Balance aus Wartungsaufwand durch unterschiedliche Systeme und perfektem Einsatz der entsprechenden Technologie entscheiden.

DatenAnforderungenProdukte
FinanzdatenLange Speicherzeit, transaktionsfähig, Sicherheit der DatenRDBS: MySQL, Postgres, OracleDB
Sitzungsdatenhohe Verfügbarkeit, skalierbar, geringe Lebensdauer, hohe Geschwindigkeit für lesende und schreibende Zugriff für eine höhere Haltbarkeit der Daten, können die Daten transparent auf ein weiteres Medium geschrieben werden.Redis, Riak, Memcache
Warenkorbdatenhohe Verfügbarkeit, skalierbarRedis, Riak
Stammdatenhohe Lesegeschwindigkeit, bei geringen SchreibvorgängenmongoDB, Noe4J
Logdateiengroße Datenmengen, lange Lebenszeit, hohe Geschwindigkeit für AuswertungCasandra
SuchindexSkalierbarkeit, hohe Geschwindigkeit bei SuchanfragenSolr

Die aktuelle Version des Frameworks kann auf der Download-Seite download.alvine.io heruntergeladen werden.

Unsere Designziele sind

Die Entwicklung von Alvine setzt auf der neusten Version von PHP (aktuell 7) auf und wird streng objektorientiert entwickelt. Die obersten Ziele sind:

  • Gut getestet
  • Objektorientierung
  • hohe Performance
  • gut dokumentiert

Unterstütze Versionen

HauptversionPHP-KompatibilitätReleasedatum
1.37PHP 7.42021-08-05
1.36PHP 7.4, PHP 7.3, PHP 7.22021-07-25
1.35PHP 7.4, PHP 7.3, PHP 7.22021-03-12
1.34PHP 7.4, PHP 7.3, PHP 7.22020-12-16
1.33PHP 7.4, PHP 7.3, PHP 7.22020-11-04
1.32PHP 7.3, PHP 7.22020-10-07
1.31PHP 7.3, PHP 7.22020-08-28

Erste Schritte

Wir laden Sie recht herzlich ein, sich mit dem Alvine-Framework zu beschäftigen. Wenn Sie neu in der Entwicklung sind, sollten Sie sich zum Start eine Entwicklungsumgebung einrichten. Sie können zum Beispiel Netbeans oder phpstorm verwenden. Als nächstes müssen Sie sich das aktuelle Phar-Archive des Alvine Frameworks besorgen. Sie können die Dateien auf download.alvine.io herunterladen.

Note

Neben der Phar-Datei brauchen Sie auch immer den mitgelieferten Schlüssel. Dieser muss im gleichen Verzeichnis wie das phar-Archive liegen.


</div>
</div>

Das Alvine PHP Framework arbeitet komplett objektorientiert. Soweit es sinnvoll ist, sind alle Funktionen in Klassen abgebildet, 
selbst für interne Typen wie Zeichenketten, Arrays und Listen stehen im Framework objektorientierte Typen bereit. Hier muss 
immer zwischen der Bequemlichkeit und der Performance gewählt werden. Im folgenden Beispiel wird statt der normalen PHP 
Stringverarbeitung mit der Alvine String-Verarbeitung gearbeitet.

```bash
wget http://download.alvine.io/alvine-framework-<version>.phar
wget http://download.alvine.io/alvine-framework-<version>.phar.pub

Konkretes Beispiel

wget http://download.alvine.io/alvine-framework-1.2.1.phar
wget http://download.alvine.io/alvine-framework-1.2.1.phar.pub

Nachdem Sie alle Bibliotheken heruntergeladen haben, können Sie mittels dem Prüfscript checkRequirements.php alle Abhängigkeiten aufspüren
und die Konfiguration anpassen. 

```bash
curl -s http://download.alvine.io/checkRequirements.php | php

PHP verwendet für die CLI und Web-Variante oftmals unterschiedliche Einstellungen. Um die gewünschte Umgebung zu testen, muss die entsprechende php.ini referenziert werden.

 curl -s http://download.alvine.io/checkRequirements.php | php -c /etc/php5/apache2/php.ini

Note

Sie können die Phar-Datei und den Schlüssel umbenennen und die Version entfernen, oder alternativ einen symbolischen Link setzen.


</div>
</div>

## Hello World

```php
/** Sie müssen das Framework einbinden */
include 'alvine.framework.phar';
/** Zeichenkette erstellen */
$string=new \Alvine\Types\StringType('Hello World!');

/** Ausgabe der Zeichenkette */
echo $string;

Das Ergebnis nach Ausführung von php hello.php auf der Kommandozeile ist dann

#linux> php hello.php
 
Hello World!

Webanwendungen

Im nächsten Schritt wollen wir einen Ausgabe im Webbrowser erzeugen. Dazu legen wir die Datei index.php im Root-Verzeichnis Ihres PHP-fähigen Webservers an.

Note

Aus Sicherheitsgründen sollten die Phar-Archive nicht im Document-Root Verzeichnis des Webservers gespeichert werden.


</div>
</div>

<!--- example:introduction/webexample.html -->
```html
<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Hallo Welt</title>
    </head>
    <body>
        <?php
        /** Sie müssen statt der 1.1.1 die von Ihnen verwendete Version nehmen */
        include 'alvine.framework-1.1.1.phar';
 
        /** String-Objekt erstellen */
        $string = new Alvine\Types\StringType('Hallo Welt!');
        /** String ausgeben */
        echo (string) $string;
        ?>
    </body>
</html>

Erstellen einer UUID

In dem nächsten Beispiel wollen wir eine UUID erstellen. Eine UUID kann als eindeutige ID eines Datensatzes genommen werden. In Alvine haben alle Objekte eine eindeutige ID. Die ID kann mit der Methode Alvine::getID() ermittelt werden.

/** UUID erstellen */
$uuid=\Alvine\Util\UUID::generateFromRandom();

/** UUID ausgeben */
echo $uuid;

Das Ergebnis ist dann eine zufällige UUID wie 87995166-93d1-400f-bb61-0317cc299233

Erstellen wir im nächsten Schritt ein Objekt - in diesem Beispiel vom Typ File - und fragen dessen UUID ab.

/** File-Objekt erstellen */
$file = new \Alvine\IO\File\File('my.txt');
 
/** UUID des Objektes ausgeben */
echo $file->getID();

Dieses Beispiel gibt die UUID des Objektes aus, z.B. b5e6d7de-9c98-4689-f288-c67d55dde885

Setup

Download

Alle notwendigen Quelltexte, Daten und Informationen zum Framework sind in einem einzigen Phar-Archive enthalten. Dieses muss per Browser, curl oder wget heruntergeladen werden. Zur Absicherung der Integrität besitzt jedes Archive eine eigene Schlüsseldatei.

Note

Die zur Version gehörende Schlüsseldatei muss im selben Verzeichnis wie das Phar-Archive liegen.

Die aktuelle Version der Komponente kann auf der Seite download.alvine.io ermittelt werden. Die letzte getestete Entwicklerversion hat die Versionsbezeichnung snapshot.

# Bibliothek
wget http://download.alvine.io/alvine.framework-<version>.phar
  
# Signatur
wget http://download.alvine.io/alvine.framework-<version>.phar.pubkey

Nach dem Download kann mit Hilfe des Test-Scripts geprüft werden, ob alle Systemvoraussetzungen für das Framework gegeben sind. Siehe hierzu auch den KB-Artikel Wie kann man überprüfen ob alle Voraussetzungen erfüllt sind?

curl -s http://download.alvine.io/checkRequirements.php | php 

Verwendung

Zur Verwendung der Komponente reicht es, dies Phar-Datei per include in das Script einzubinden.

include /path/alvine.framework-<version>.phar

exec vs system vs passthru vs shell_exec

Übersicht

FunktionParameterRückgabewert
execstring $command,
[array &$output],
[int &$return_var]
string
passthrustring $command,
[int &$return_var]
void
shell_execstring $cmdstring
systemstring $command,
[int &$return_var]
string
FunktionDirekte AusgabeAusgabe als VariableExitcode als Variable
execneinarray (Parameter $output),
string (letzte Zeile)
ja (Parameter return_var)&$
systemja (text)string (letzte Zeile)ja
shell_execneinstring (Rückgabe)nein
passthruja (raw)neinja (Parameter &$return_var)

Befehle

exec

exec() führt einen angegebenen Befehl (command) aus.

$result = \exec('whoami');

passthru

Führt ein externes Programm aus und zeigt dessen Ausgabe an.

// Ausgabe direkt an Console oder Browser
\passthru("cat myfile", $err);

system

Die Funktion system() ähnelt der C Version der Funktion sehr, indem es einen übergebenen Befehl ausführt und dessen Ausgabe anzeigt.

Wird ein system()-Funktionsaufruf durchgeführt, versucht die Funktion automatisch, nach jeder Ausgabezeile den Ausgabepuffer zu flushen, sofern PHP als Servermodul läuft.

Wenn Sie einen Befehl ausführen wollen und die erzeugten Daten ohne Behinderung direkt zurückgeben wollen, verwenden Sie stattdessen die Funktion passthru().

$lastLine = \system('ls', $retval);

shell_exec

Diese Funktion ist identisch zum Backtick-Operator.

PHP unterstützt einen Operator zur Ausführung externer Programme: Die sog. Backticks.

Note

Die Backticks sind keine einfachen Anführungszeichen! PHP versucht, den Text zwischen den Backticks als Kommandozeilen-Befehl auszuführen. Die Ausgabe des aufgerufenen Programms wird zurückgegeben (d.h. wird nicht einfach ausgegeben, sondern kann einer Variablen zugewiesen werden).


</div>
</div>
    
Die Verwendung des Backtick-Operators ist mit shell_exec() identisch.

$output = \shell_exec(‘ls -lart’);


Dataset

Ein Dataset ermöglicht es verschiedene Objekte zentral und in einer Hirarchie abzubilden. Im wesentlichen besteht ein Dataset aus Maps, Collections und Alvine-Objekten.

Allgemeine Definition

$dataset=(new \Alvine\Data\Dataset) 
    ->setValue('my', new \Alvine\Types\StringType('World'))
    ->setValue('user', (new \Alvine\Types\Collection())
    ->append((new \Alvine\Types\Map)->setValue('name', 'Hans')->setValue('plz', '12'))
    ->append((new \Alvine\Types\Map)->setValue('name', 'Franz')->setValue('plz', '34'))
    ->append((new \Alvine\Types\Map)->setValue('name', 'Thomas')->setValue('plz', '56'))
    ->append((new \Alvine\Types\Map)->setValue('name', 'Alexander')->setValue('plz', '78')));

// Json erstellen        
echo \json_encode($dataset);

Je nach Typ des Objektes wird das Ergebnis unterschieldich sein. In diesem Beispiel erhält man folgende Zeichenkette:

{"my":"World","user":[{"name":"Hans","plz":"12"},{"name":"Franz","plz":"34"},{"name":"Thomas","plz":"56"},{"name":"Alexander","plz":"78"}]}

Json Ausgabe

Wird keine Map verwendet, so werden nacheinander verschiedene Methoden zur Wertbestimmung probiert. Zuerst wird geprüft ob das Objekt das Interface \JsonSerializable implementiert. Ist dies der Fall, so wird das Ergebnis verwendet. Im weiteren wird noch auf

  • toArray(),

  • asJson() und

  • __toString()

  • in dieser Reihenfolge - geprüft. Ist keine der Methoden vorhanden, so werden alle öffentlichen Eigenschaften genommen. Wird keine dieser Optionen gefunden, so wird null verwendet.

Im folgenden Beispiel sind einige Varianten zusammengestellt

/** Objekt ohne Methoden und Daten */
class MyData {
    
}

/** Dataset */
$dataset=(new \Alvine\Data\Dataset)
    ->setValue('entry', new MyData());

/** JSON */
echo \json_encode($dataset);
// Ergibt -> {"entry":[]}

/** Objekt ohne Methoden und Daten */
class MyDataToString {

    public function __toString() {
        return 'value';
    }

}

/** Dataset */
$dataset=(new \Alvine\Data\Dataset)
    ->setValue('entry', new MyDataToString());

/** JSON */
echo \json_encode($dataset);
// Ergibt -> {"entry":"value"}

/** Objekt ohne Methoden und Daten */
class MyDataToArray {

    public function toArray() {
        return ['value'];
    }

}

/** Dataset */
$dataset=(new \Alvine\Data\Dataset)
    ->setValue('entry', new MyDataToArray());

/** JSON */
echo \json_encode($dataset);
// Ergibt -> {"entry":["value"]}

/** Objekt ohne Methoden und Daten */
class MyDataAsJson {

    public function asJson() {
        return \json_encode(['value']);
    }

}

/** Dataset */
$dataset=(new \Alvine\Data\Dataset)
    ->setValue('entry', new MyDataAsJson());

/** JSON */
echo \json_encode($dataset);
// Ergibt -> {"entry":["value"]}

/** Objekt ohne Methoden und Daten */
class MyDataProperties {

    public $myPublic='World';
    protected $myProtected='Privat';

}

/** Dataset */
$dataset=(new \Alvine\Data\Dataset)
    ->setValue('entry', new MyDataProperties());

/** JSON */
echo \json_encode($dataset);
// Ergibt -> {"entry":{"myPublic":"World"}}

Messgrößen

Die Klassen im Namespace \Alvine\Measure stellen Methoden und Konstanten für den Umgang mit Messgrößen, Maßsystemen und deren Berechnung bereit.

Einheiten

Einheiten werden mit der Unit-Klasse abgebildet und repräsentieren Maßeinheiten wie Kg, Sekunde, Liter oder Meter. Eindeutige Größen haben unterschiedliche Einheiten und es besteht zwischen Ihnen meistens kein Zusammenhang.

::uml::

skinparam monochrome true skinparam shadowing false

abstract class \Alvine\Measure\Unit extends \Alvine\Core\Alvine class \Alvine\Measure\ProductUnit extends \Alvine\Measure\DerivedUnit class \Alvine\Measure\AlternateUnit extends \Alvine\Measure\DerivedUnit class \Alvine\Measure\TransformedUnit extends \Alvine\Measure\DerivedUnit class \Alvine\Measure\BaseUnit extends \Alvine\Measure\Unit abstract class \Alvine\Measure\DerivedUnit extends \Alvine\Measure\Unit class \Alvine\Measure\CompoundUnit extends \Alvine\Measure\DerivedUnit

::end-uml::

Für SI-Einheiten existieren Methoden um die Einheiten zu erstellen. In dem Beispiel wird auf definierte SI-Einheiten zugegriffen.

\Alvine\Measure\SI::meter();
\Alvine\Measure\SI::candela();
\Alvine\Measure\SI::kilogram();
\Alvine\Measure\SI::kelvin();

Im folgenden Beispiel wird eine eigene Längen-Einheit [myl] definiert.

$mylength=new \Alvine\Measure\BaseUnit("myl");
echo $mylength->getDimension();
// -> [myl]

Mehrere Einheiten sind in einem Einheiten-System zusammengefasst. Das bekannteste Einheitensystem ist das SI-System.

::uml::

skinparam monochrome true skinparam shadowing false

class \Alvine\Core\Singleton class \Alvine\Measure\SystemOfUnits extends \Alvine\Core\Singleton class \Alvine\Measure\SI extends \Alvine\Measure\SystemOfUnits class \Alvine\Measure\NonSI extends \Alvine\Measure\SystemOfUnits class \Alvine\Core\Singleton extends \Alvine\Core\Alvine class \Alvine\Measure\Economics\Currencies extends \Alvine\Measure\SystemOfUnits

::end-uml::

Jedes Einheiten-System besitzt ein Modell. Dieses Modell stell zum einen die Einheiten und zum anderen den Konverter zum umrechnen von Einheiten bereit.

::uml::

skinparam monochrome true skinparam shadowing false

interface \Alvine\Measure\Model class \Alvine\Core\Alvine class \Alvine\Measure\SI\StandardModel extends \Alvine\Core\Alvine class \Alvine\Measure\SI\StandardModel implements \Alvine\Measure\Model

::end-uml::

Dimension

Einheiten können zu Dimensionen zusammengefasst werden. Dimensionen kann man auch als Einheitenart oder -klasse betrachten.

::uml::

skinparam monochrome true skinparam shadowing false

interface \Alvine\Measure\Model

class \Alvine\Measure\SI\StandardModel extends \Alvine\Core\Alvine class \Alvine\Measure\SI\StandardModel implements \Alvine\Measure\Model class \Alvine\Measure\Dimension extends \Alvine\Core\Alvine

::end-uml::

Über die Methode \Alvine\Measure\Dimension::setModel($model) muss ein Modell gesetzt werden. Es kann immer nur ein aktives Modell gesetzt sein, wir kein Modell gesetzt so wird das Standard-SI-Modell verwendet.

Ein Modell muss das Interface Alvine\Measure\Model implementieren und die Dimension und einen Konverter zurückliefern.

Mit Hilfe des Konverters kann man innerhalb des Einheitesystems umrechnungen vornehmen.

Im dem folgenden Beispiel werden Meilen in Kilometer umgerechnet. Dazu wird ein Konverter benutzt.

Für beide Größen werden in der Methode \Alvine\Measure\Unit::getConverterTo() die Dimensionen geholt und über die Demensionen der Konverter zur Umrechnung.

// Längeneinheit Meter
$meter=\Alvine\Measure\SI::METER();

// Meile als Teiler von Metern
$mile=$meter->times(1609344)->divide(1000);

// Konverter Meilen nach Meter
$mileToKilometer=$mile->getConverterTo(\Alvine\Measure\MetricPrefix::kilo($meter));

// Zu berechnende Meilen
$distanceInMiles=23.0;

$distanceInKilometers=$mileToKilometer->convert($distanceInMiles);

// Ausgabe
echo $distanceInKilometers;
// -> 37.014912

Präfixe

Die Klasse MetricPrefix stellt statische Methoden zur Darstellung von Größenordnungen von Einheiten bereit.

::uml::

skinparam monochrome true skinparam shadowing false

class \Alvine\Core\Alvine class \Alvine\Measure\BinaryPrefix extends \Alvine\Core\Alvine abstract \Alvine\Measure\MetricPrefix extends \Alvine\Core\Alvine

::end-uml::

$meter=\Alvine\Measure\SI::METER();
$kilometer=\Alvine\Measure\MetricPrefix::kilo($meter);

echo (string) $kilometer->getDimension();
// -> [L]
echo (string) $kilometer;
// -> (m)*1000

Weitere Details können hier nachgelesen werden.

Messwert

Die Klassen \Alvine\Measure\Amount stellt die zentrale Klasse für den Umgang mit Werten da.

::uml::

skinparam monochrome true skinparam shadowing false

class \Alvine\Measure\Amount implements \Alvine\Measure\Measurable class \Alvine\Measure\Amount extends \Alvine\Core\Alvine

interface \Alvine\Measure\Measurable

::end-uml::

in dem ersten Beispiel soll die Messung einer Länge abgebildet werden. Es werden 24 Meter gemessen und in dem Wert \Alvine\Measure\Amount mit der Einheit Meter gespeichert.

$amount=\Alvine\Measure\Amount::valueOf(24, \Alvine\Measure\SI::METER());
echo (string) $amount;
// -> 24 m

Wie man hier sieht, kann eine Instanz nicht über einen Konstruktor erstellt werden. Eine neues Objekt muss über die Methode \Alvine\Measure\Amount::getValue() erstellt werden.

Messwert können exakte Werte oder Näherungswerte enthalten. Mit der Methode \Alvine\Measure\Amount::isExact() lässt sich prüfen, ob ein Messwert exakt ist.

Über die Methode \Alvine\Measure\Amount::getExactValue() lässt sich der exakte Wert - ein Integer - abfragen.

Speichert man Float-Werte, so ergibt sich immer eine Ungenauigkeit. Diese liegt im Bereich pow(2, -53). Bei ungenauen Werten erfolgt die Abfrage des Wertes über \Alvine\Measure\Amount::getEstimatedValue().

Wird ein exaktes Messergebnis mit \Alvine\Measure\Amount::getEstimatedValue() abgefragt, so wird der Wert von \Alvine\Measure\Amount::getExactValue() zurückgegeben.

Über die beiden Methoden \Alvine\Measure\Amount::getMinimumValue() und \Alvine\Measure\Amount::getMaximumValue() lässt sich die Abweichung abfragen.

Der absolute und der relative Fehler lässt sich über die Methode \Alvine\Measure\Amount::getAbsoluteError() und \Alvine\Measure\Amount::getRelativeError() anfordern.

Über die Methode \Alvine\Measure\Amount::to() lassen sich Einheiten umrechnen.

Präfixe

Präfixes oder Vorsilben werden einer Einheit vorangestellt und erlauben so Vielfache oder Teile von Maßeinheiten zu bilden. Dadurch lassen sich Zahlen mit vielen Stellen vermeiden.

Die statische Methode \Alvine\Measure\SI::METER() gibt ein Objekt der Klasse \Alvine\Measure\SI\Unit\Length zurück. Mit Hilfe der Funktion \Alvine\Measure\MetricPrefix::milli() wird ein neues Objekt vom Typ \Alvine\Measure\TransformedUnit erstellt.

echo (string) \Alvine\Measure\MetricPrefix::milli(\Alvine\Measure\SI::METER());
// -> (m)/1000

Zur Darstellung des Ergebnisses ist die Einheit m/1000 nicht schön und sollte durch die Normdarstellung mm ersetzt werden.

echo (string) (\Alvine\Measure\MetricPrefix::milli(\Alvine\Measure\SI::METER()))
    ->alternate('mm');
// -> mm

echo (string) (\Alvine\Measure\MetricPrefix::milli(\Alvine\Measure\SI::METER()))
    ->alternate(\Alvine\Measure\SI::MILLIMETER);
// -> mm

Für Millimeter gibt es die hier gezeigte Funktion bereits als statische Methode.

echo (string) \Alvine\Measure\SI::millimeter();
// -> mm

Auf dieser Seite sind einige Beispiele zur Umrechnung von Einheiten aufgeführt.

Fuß

In diesem Beispiel wird die Einheit Fuß in Kilometer umgerechnet.

// Fuß
$foot=\Alvine\Measure\NonSI::foot();

// Konverter Meilen nach Meter
$footToKilometer=$foot->getConverterTo(\Alvine\Measure\MetricPrefix::kilo(\Alvine\Measure\SI::meter()));

// Zu berechnende Fuß
$distanceInFoot=4356;

/**
 * Auf zwei Stellen gerundet
 */
$distanceInKilometers=$footToKilometer->convert($distanceInFoot, 2);

// Ausgabe
echo $distanceInKilometers;
// -> 1.33 km

Miles

Dieses Beispiel zeigt die Umrechnung von Miles in Kilometer.

// Längeneinheit Meter
$meter=\Alvine\Measure\SI::meter();

// Meile als Teiler von Metern
$mile=\Alvine\Measure\NonSI::mile();

// Konverter Meilen nach Meter
$mileToKilometer=$mile->getConverterTo(\Alvine\Measure\MetricPrefix::kilo($meter));

// Zu berechnende Meilen
$distanceInMiles=23.0;

// Drei Stellen gerundet
$distanceInKilometers=$mileToKilometer->convert($distanceInMiles, 3);

// Ausgabe
echo $distanceInKilometers;
// -> 37.015

Dimmensionslos

Beim Rechnen mit dimmensionslosen Zahlen kann die Klasse \Alvine\Measure\Unit\One verwendet werden. Eine Instanz der Klasse kann über die Methode \Alvine\Measure\Unit::getOne() geholt werden.

$measurement=\Alvine\Measure\Amount::valueOf(1000, \Alvine\Measure\SI::METER());


$factor=\Alvine\Measure\Amount::valueOf(2, \Alvine\Measure\Unit::getOne());
$divisor=\Alvine\Measure\Amount::valueOf(5, \Alvine\Measure\Unit::getOne());

$result=$measurement->times($factor)->divide($divisor);

echo (string) $result;
// -> 400 m

Maßeinheiten

Geld, als allgemeines Tausch und Zahlungsmittel, gibt es in unterschiedlichen Formen und Ausprägungen. Die Währungs-Klassen aus dem Framework bieten für viele Vorgänge eine passende Lösung an. Insbesondere für die Berechnung von Preis pro Fläche oder pro pro Menge stehen Berechnungs- und Konvertierungsfunktionen bereit.

Währungen

Eine Währung ermöglicht den Transfer von Waren und Dienstleistungen, ohne eine Gegenleistung in Form von anderen Waren und Dienstleistungen zu liefern. Die Währungen stehen als Klassen zur Verfügung.

// Währungsobjekte holen
$USD=\Alvine\Measure\Economics\Currencies::getCurrency('USD');
$EUR=\Alvine\Measure\Economics\Currencies::getCurrency('EUR');

Währungen ausgeben

Währungen werden auch für die Ausgabe benötigt. Hierzu kann man mit dem Einheiten-Formatter die Währung als Einheit übergeben. Einige Währungen besitzen neben dem offiziellen Kürzel auch ein Währungszeichen. So steht das für den Euro und das $-Zeichen für den Dollar.

// Währungssymbole im Formatter setzen
\Alvine\Measure\Formatter\UnitFormatter::getDefaultFormatter()
    ->label($USD, \Alvine\I18n\Resource\CurrencySigns::getSign('USD'));

\Alvine\Measure\Formatter\UnitFormatter::getDefaultFormatter()
    ->label($EUR, \Alvine\I18n\Resource\CurrencySigns::getSign('EUR'));

Der Formatter für die Währungen besitzt drei Platzhalter: amount, unit und error. error ist der beim Runden oder berechnen entstandene Fehler. Der Formatter verwendet intern die PHP-Methode vsprintf. Das s hinter dem $-Zeichen im Format bedeutet laut PHP-Dokumentation: “s - das Argument wird als String angesehen und auch als solcher ausgegeben.”

/** Formatter definieren */
\Alvine\Measure\Formatter\AmountFormatter::getDefaultFormatter()
    ->setFloatFormat('%amount$s %unit$s');

Rechnen mit Geldbeträgen

Währungsbeträge können über die Funktionen Money::times(), Money::divide(), Money::plus() und Money::minus() multipliziert, dividiert, addiert und subtrahiert werden. Über Money::root() und Money::pow() kann zudem die Quadratwurzel gezogen und der Exponent berechnet werden.

Diese Methode bieten im Gegensatz zur direkten Berechnung den sicheren Umgang mit Fehlern und Genauigkeit.

// Währungsobjekte holen
$EUR=\Alvine\Measure\Economics\Currencies::getCurrency('EUR');

/** Formatter definieren */
\Alvine\Measure\Formatter\AmountFormatter::getDefaultFormatter()->setFloatFormat('%amount$s %unit$s');

/** Ausgabe mit Sonderzeichen $ und € statt USD und EUR */
\Alvine\Measure\Formatter\UnitFormatter::getDefaultFormatter()->label($EUR, \Alvine\I18n\Resource\CurrencySigns::getSign('EUR'));


/** Geldbetrag */
$spending=\Alvine\Measure\Economics\Money::valueOf(5.56, $EUR);

/** Ausgabe */
echo((string) $spending."\n");
// Ausgabe -> 5.56 €

// Wert teilen
$spending=$spending->divide(10);
echo((string) $spending."\n");
// Ausgabe -> 0.56 €
// Wert multiplizieren

$spending=$spending->times(100);
echo((string) $spending."\n");
// Ausgabe -> 55.60 €
// Werte addieren

$spending=$spending->plus(\Alvine\Measure\Economics\Money::valueOf(65.56, $EUR));
echo((string) $spending."\n");
// Ausgabe -> 121.16 €

Umrechnung

// Währungsobjekte holen
$USD=\Alvine\Measure\Economics\Currencies::getCurrency('USD');
$EUR=\Alvine\Measure\Economics\Currencies::getCurrency('EUR');

/** Wechselkurs 8.5.2018 */
$rate=1.19340;

// Wechselkurs läuft immer über eine Referenzwährung
\Alvine\Measure\Economics\ReferenceCurrency::setCurrentReference($USD);
\Alvine\Measure\Economics\ReferenceCurrency::getCurrentReference()->setExchangeRate($EUR, $rate);

/** Geldbetrag */
$spending=\Alvine\Measure\Economics\Money::valueOf(5.56, $USD);

/** Wechseln */
$spendingInEur=$spending->to($EUR);

/** Formatter definieren */
\Alvine\Measure\Formatter\AmountFormatter::getDefaultFormatter()->setFloatFormat('%amount$s %unit$s');

/** Ausgabe mit Sonderzeichen $ und € statt USD und EUR */
\Alvine\Measure\Formatter\UnitFormatter::getDefaultFormatter()->label($USD, \Alvine\I18n\Resource\CurrencySigns::getSign('USD'));
\Alvine\Measure\Formatter\UnitFormatter::getDefaultFormatter()->label($EUR, \Alvine\I18n\Resource\CurrencySigns::getSign('EUR'));


/** Ausgabe */
echo("".$spending." ≙ ".$spendingInEur."");

// Ausgabe -> 5.56 USD ≙ 4.66 EUR

Beispiel

// Währungsobjekte holen
$USD=\Alvine\Measure\Economics\Currencies::getCurrency('USD');
$EUR=\Alvine\Measure\Economics\Currencies::getCurrency('EUR');

// Währungssymbole holen und setzen
\Alvine\Measure\Formatter\UnitFormatter::getDefaultFormatter()->label($USD, \Alvine\I18n\Resource\CurrencySigns::getSign('USD'));
\Alvine\Measure\Formatter\UnitFormatter::getDefaultFormatter()->label($EUR, \Alvine\I18n\Resource\CurrencySigns::getSign('EUR'));

// Wechselkurs läuft immer über eine Referenzwährung
\Alvine\Measure\Economics\ReferenceCurrency::setCurrentReference($USD);
\Alvine\Measure\Economics\ReferenceCurrency::getCurrentReference()->setExchangeRate($EUR, 1.17);

// Angabe des Verbrauchs in Meilen pro Galone (20 mi/gal)
$carMileage=\Alvine\Measure\Amount::valueOf(20, \Alvine\Measure\NonSI::mile()->divide(\Alvine\Measure\NonSI::gallonLiquidUS())); 
// Kosten des Benzins in Euro / Liter (1.2 €/L)
$gazPrice=\Alvine\Measure\Amount::valueOf(1.2, $EUR->divide(\Alvine\Measure\NonSI::liter())); 
// Gefahrene Distanz in Kilometern (400 km)
$tripDistance=\Alvine\Measure\Amount::valueOf(400, \Alvine\Measure\MetricPrefix::kilo(\Alvine\Measure\SI::meter())); 

// Kosten der Reise in Euro
$tripCostEUR=$tripDistance->divide($carMileage)->times($gazPrice)->to($EUR);
// Kosten in Dollar (Umrechung)
$tripCostUSD = $tripCostEUR->to($USD);
 
// Displays cost.
echo("Car Miles = $carMileage\n");
// -> Car Miles = 20 mi/gal
echo("Gas Prize = $gazPrice\n");
// -> Gas Prize = (1.20 ± 0.0) €/l
echo("Trip Distance = $tripDistance\n");
// -> Trip Distance = 400 (m)*1000
echo ("============================\n");
// -> ============================
echo("Trip cost = ".$tripCostEUR." (".$tripCostUSD.")\n");
// ->  Trip cost = (56.45 ± 0.0) € ((66.05 ± 0.0) $)

// Ausgabe ohne Fehler
\Alvine\Measure\Formatter\AmountFormatter::getDefaultFormatter()->setFloatFormat('%amount$s %unit$s');
echo("Trip cost = ".$tripCostEUR." (".$tripCostUSD.")");

Title: Zeit- und Datumsberechnung

Zeit- und Datumsberechnung

Alvine biete mit den Klassen aus dem date-Namespace einige nützliche Funktionen zur Zeit- und Datumsberechnung an.

Grundlagen

Die Basis der Zeitberechnung ist die Klasse Quantity. Diese Klasse stellt alle notwendigen Funktionen und Eigenschaften für die Arbeit mit Datum und Zeit zur Verfügung.

$instant = new \Alvine\Date\Instant(1920, 12, 24);
echo (string)$instant; // 1920-12-24 00:00:00
 
 
// Tag der Mondlandung
$instant = new \Alvine\Date\Instant(1969, 7, 21, 2, 56, 20);
echo (string)$instant; // 1969-07-21T02:56:20

// Es erfolgt kein Überlauf, beim Überschreiten
// der einzelnen Bereiche
$instant = new \Alvine\Date\Instant(99999, 9999, 9999, 9999, 9999, 9999);
echo $instant; // 99999-9999-9999T9999:9999:9999
 
$quantity = new \Alvine\Date\DateTime();
echo (string)$quantity; // 0000-01-01T00:00:00

Eine konkrete Ableitung von Quantity ist die Instant-Klasse, sie definiert einen Moment, der über die Parameter Jahr, Monat, Tag, Stunde, Minute, Sekunde und Nanosekunde definiert ist. Die Klassen Date, Time und Datetime sind konkrete Ableitungen der Klasse AbstractDateTime, die wiederum von Instant abgeleitet ist.

Aktuelle Zeit

Wird für die weitere Verarbeitung die aktuelle Zeit benötigt, so kann dieser über die Methode fromNow() geholt werden.

$timestamp = \Alvine\Date\Time::fromNow();
print_r($timestamp);
echo $timestamp;echo "\r\n";
echo date('Y.m.d H:i:s');

Berechnungen

Mit den Methoden plus und minus kann Zeit angepasst werden. Es wird ein neues Objekt zurück geliefert. Das aktuelle Objekt behält die Zeit.

Beispiele:

Wert
1 day
2 days
2 weeks
3 months
4 years
1 year + 1 day
1 day + 12 hours
3600 seconds

plus

Zeit hinzu zählen

/**
 * aktueller Zeitpunkt
 */
 $now=\Alvine\Date\DateTime::fromNow(); //2018-08-28 05:42:00
 
/**
 * einen Tag dazu rechnen
 */
 $nextDay = $now->plus('1 day');
 
 echo $now; //2018-08-28 05:42:00
 echo $nextDay; //2018-08-29 05:42:00

minus

Zeit hinzu zählen

/**
 * aktueller Zeitpunkt
 */
 $now=\Alvine\Date\DateTime::fromNow(); //2018-08-28 05:42:00
 
/**
 * einen Tag dazu rechnen
 */
 $yesterday = $now->minus('1 day');
 
 echo $now; //2018-08-28 05:42:00
 echo $yesterday; //2018-08-27 05:42:00

Spezialisierungen

Um bestimmte Sachverhalte besser abzubilden und unnötige Informationen auszublenden, gibt es Spezialklassen, wie die YearMonth-Klasse. Die JahrMonats-Klasse eignet sich zum Beispiel zum Speichern des “Gültig bis”-Datums von Kreditkarten.

// Gültig bis Dezember 2020
$valid=new \Alvine\Date\YearMonth(2020, 12);
echo (string) $valid; // 2020-12

Sekunden

Die Klasse Seconds bildet einen Sonderfall, sie ist nicht von Time, sondern von Instant abgeleitet und kann somit nicht nur Zahlen von 0 bis 59 aufnehmen, sondern auch komplette Timestamps.

Wochentag

Die Klasse DayOfWeek bildet den Wochentag ab.

WertTag
1Montag
2Dienstag
3Mittwoch
4Donnerstag
5Freitag
6Samstag
7Sonntag
/**
 * aktueller Zeitpunkt
 */
$now=\Alvine\Date\DateTime::fromNow(); //2018-08-28 05:42:00

/**
 * Tag der Woche bestimmen
 */
$day=\Alvine\Date\DayOfWeek::getInstanceFromDate(new \Alvine\Date\Date(1990, 12, 24));
echo $day->getValue(); // 1

Lokalisiertes Datum ausgeben

Die Ausgabe eines Datum kann über folgende Anweisung erfolgen.

$now=\Alvine\Date\DateTime::fromNow();

echo \sprintf('%3$02d.%2$02d.%1$02d %4$02d:%5$02d:%6$02d',
    $now->getYear(),
    $now->getMonth(),
    $now->getDay(),
    $now->getHour(),
    $now->getMinute(),
    $now->getSecond());

Locale

Die Locale-Klasse stellt eine zentrale Möglichkeit zur Definition von Länder-, Sprach- und Kulturunterschieden zur Verfügung. Eine neue Locale erhält man über die Factory-Methode get().

Je PHP-Prozess gibt es für jeden Locale-Tag ein einziges Locale-Objekt.

$locale=\Alvine\i18n\Locale::getInstance('de-Latn-DE');
echo $locale->getDisplayName('it_CH'); // gibt ⇢ tedesco (latino, Germania) aus
echo $locale->getDisplayName('en_GB'); // gibt ⇢ German (Latin, Germany) aus

Encoding

Mit der Encoding-Klasse ist es möglich Zeichenketten von einem Encoding in ein anderes umzuwandeln. Als PHP-Funktion kommt mb_convert_encoding zum Einsatz.

Bei der ersten Verwendung dieser Klasse, muss eine Instanz erstellt werden. Dies kann über die Methode Encoding::getInstance() erfolgen. Dies sorgt dafür, dass die entsprechenden \mb_string Einstellungen korekt gesetzt werden.

Die Methode Encoding::encode() Wandelt eine Zeichenkette in das gewünschte Zielencoding um und gibt diese Zeichenkette zurück.

$encoding=\Alvine\I18n\Encoding::getInstance();

echo $encoding->encode('Iñtërnâtiônàlizætiøn', \Alvine\I18n\Encoding::BASE64);
// ⇢ ScOxdMOrcm7DonRpw7Ruw6BsaXrDpnRpw7hu

echo $encoding->encode('Iñtërnâtiônàlizætiøn', \Alvine\I18n\Encoding::HTML_ENTITIES);
// ⇢ I&ntilde;t&euml;rn&acirc;ti&ocirc;n&agrave;liz&aelig;ti&oslash;n

echo $encoding->encode('Iñtërnâtiônàlizætiøn', \Alvine\I18n\Encoding::CP1252);
// ⇢ I�t�rn�ti�n�liz�ti�n

Wird kein gültiges Encoding übergeben, so wird eine \Alvine\I18n\EncodingException geworfen.

try {
    echo $encoding->encode('Iñtërnâtiônàlizætiøn', 'unsupported!');
} catch(\Alvine\I18n\EncodingException $e) {
    echo $e->getMessage();
}

Formatter

Die I18n\MessageFormatter-Klasse ist eine Erweiterung der Text\MessageFormatter-Klasse. Sie formatiert einen vorgegebenen Text und ersetzt die übergebenen Platzhalter.

Die Standardbegrenzer sind { und }. Diese können aber einfach durch die Methode MessageFormatter::setMarker($start, $end) ersetzt werden.

$locale=\Alvine\I18n\Locale::getInstance('de');
$text="Wir kaufen {n} Autos";
$map=new Alvine\Types\Map();
$map['n']='5';
$f=new \Alvine\I18N\MessageFormatter($locale, $map);
echo (string) $f->format($text);
// ⇢ Wir kaufen 5 Autos

Kommen die Begrenzer in dem zu formatierenden Text vor, so müssen diese mit einem \ escaped werden.

$locale=\Alvine\I18n\Locale::getInstance('de');
$text="Wir kaufen \{n\} Autos";
$map=new Alvine\Types\Map();
$map['n']='5';
$f=new \Alvine\I18N\MessageFormatter($locale, $map);
echo (string) $f->format($text);
// ⇢ Wir kaufen {n} Autos

In dem Beispiel wird START und END als Begrenzer verwendet und einmal escaped und nur einmal ersetzt.

$locale=\Alvine\I18n\Locale::getInstance('de');

$map=new \Alvine\Types\Map([
    'fahren'=>'ok',
    'object'=>'Auto']);

$m=new \Alvine\I18N\MessageFormatter($locale, $map);
$m->setMarker('START', 'END');
echo $m->format('Wir \STARTfahren\END mit dem STARTobjectEND');
// ⇢ Wir STARTfahrenEND mit dem Auto

Für Plathalter können auch Callback-Funktionen definiert werden. Somit ist es möglich die Ersetzung frei zu programmieren.

$locale=\Alvine\I18n\Locale::getInstance('de');
$map=new \Alvine\Types\Map();
$m=new \Alvine\I18N\MessageFormatter($locale, $map);
// ⇢ Der Methodenname ist Casesesitive. $m->ondiph würde nicht aufgerufen.
$m->onDiph=function($obj, $value) {
    return "Auto";
};
echo (string) $m->format('{Diph}haus'); // Autohaus.
// ⇢ Autohaus

Als Erweiterung zur Text\MessageFormatter-Klasse kann die I18n\MessageFormatter-Klasse mit Pluralregeln umgehen.

$locale=\Alvine\I18n\Locale::getInstance('de');

$map=new \Alvine\Types\Map([
    'anzahl'=>1,
    'fahren'=>'ok',
    'object'=>'Auto']);

$m=new \Alvine\I18N\MessageFormatter($locale, $map);
$m->setMarker('START', 'END');

$prop=new \Alvine\Types\Properties();
$text=new \Alvine\I18n\PropertyText($prop, 'anzahl');

echo $m->format($text, null, 'anzahl');

Als Erweiterung zur Text\MessageFormatter-Klasse kann die I18n\MessageFormatter-Klasse mit Pluralregeln umgehen.

Mit der Methode MessageFormatter::format($text, \Alvine\Types\Map $map=null, $pluralRuleKey=null) lässt sich ein Text formatieren. Dazu muss als erstes Argument die Zeichenkette und als zweiter Parameter optional eine Map übergeben werden.

Als letzter Parameter kann noch bestimmt werden ob in dem Satz ein Zahlwort für die Bestimmung der Mehrzahlregelung vorkommt. Wird eine Mehrzahlregel verwendet, so muss der Text vom Typ \Alvine\I18n\PropertyText sein.

PropertyText

Die PropertyText-Klasse erlaubt die Definition von Texten, die über einen Schlüssel referenzierbar sind.

Je nach Sprache gibt es unterschiedliche Mehrzahlregelungen für Hauptwörter und Einheiten (Stunde und Stunden). Einige Sprachen wie Englisch haben zwei Formen, andere Sprachen haben eine oder viele Regeln.

Die Pluralregeln werden in die folgendne Kategorien eingeteil:

  • zero
  • one (Einzahl)
  • two (Dual)
  • few (Paucal)
  • many
  • other

Die Kategorien sind in der \Alvine\I18n\Util\PluralRules-Klasse als Konstanten definiert.

$property=new \Alvine\Types\Properties();
$property->setValue('text', 'Es gibt {count} Autos');
$property->setValue('text.ONE', 'Es gibt {count} Auto');
$text=new \Alvine\I18n\PropertyText($property, 'text');

Ein Formatter erlaubt die Formierung des ausgwählten Textes.

// Lokale bestimmt die Mehrzahlregel
$locale=\Alvine\I18n\Locale::getInstance('de');

// Einen MessageFormatter definieren
$formatter=new \Alvine\I18N\MessageFormatter($locale);

In diesem Beispiel wird die Anzahl auf 0 gesetzt.

// Über die Map wird dann gesteuert, welcher
// Text als Vorlage genommen werden soll.
$mapZero=new \Alvine\Types\Map(['count'=>0]);
echo $formatter->format($text, $mapZero, 'count');
// Es gibt 0 Autos

In diesem Beispiel wird die Anzahl auf 1 gesetzt.

$mapOne=new \Alvine\Types\Map(['count'=>1]);
echo $formatter->format($text, $mapOne, 'count');
// ⇢ Es gibt 1 Auto

In diesem Beispiel wird die Anzahl auf 2 gesetzt.

$mapTwo=new \Alvine\Types\Map(['count'=>2]);
echo $formatter->format($text, $mapTwo, 'count');
// ⇢ Es gibt 2 Autos

MessageArgumentFormatter

Mit der MessageArgumentFormatter-Klasse ist es möglich Zeichenketten die sowohl einen Schlüssel, als auch Schlüsselwertpaare besitzen zu formatieren.

Die Klasse I18n\MessageArgumentFormatter ist von Text\MessageArgumentFormatter abgeleitet und erbt die Funktionalität.

Besitzt ein Parameter den Schlüssel pluralrulekey, so wird der Wert des Schlüssels als Zahlenwert für die Pluralregeln genommen.

i18n.localekey::count=1::amount=3::pluralrulekey=count

i18n.localekey definiert den Schlüssel im PropertyText-Template. Als Schlüssel für die Entscheidung welche Zahl als Pluralregel genommen werden soll dient pluralrulekey. Hier wird der Schlüssel count mit dem Wert 1 als Plural regel definiert.

Sollte stattdessen amount als Auslöser für die Mehrzahl genommen werden, müsste man pluralrulekey=amount definieren.

Text

Ein Textobjekt speichert einen beliebigen Text ab. Im Konstruktor kann entweder eine Zeichenkette oder ein Objekt mit implementierter __toString() Methode sein.

Note

Dieses Objekt speichert nur den Text und nicht das Objekt. Das bedeutet, das der Text zum Zeitpunkt der Objekterstellung statisch ist und Änderungen am Ausgangsobjekt keine Auswirkungen haben.

$textA=new \Alvine\Text\Text('Das ist ein Test');
echo (string) $textA."\n"; 
// ⇢ Das ist ein Test

$obj=new \Alvine\Types\StringType('Test');
$textB=new \Alvine\Text\Text($obj);

// Bei Erstellen des Objektes, wird der 
// Text statisch gespeichert.
echo $textB."\n";
// ⇢ Test

// Wert im StringType Objekt ändern
$obj->string='99';
 // Die Ausgabe ist noch immer Test und nicht 99;
echo $textB."\n"; 
// ⇢ Test

Formater

Mit Hilfe der Klasse \Alvine\Text\MessageFormatter lassen sich sehr einfach Ersetzungen durchführen. In dem folgenden einfachen Beispiel wird einfach ein Platzhalter ersetzt.

$text="Wir kaufen {n} Autos";
$map=new Alvine\Types\Map();
$map['n']='5';
$f=new \Alvine\Text\MessageFormatter($map);
echo (string) $f->format($text);
// ⇢ Wir kaufen 5 Autos

Soll der Platzhalter nicht ersetzt werden, so können die Sonderzeichen maskiert werden. Hierzu ist dem Sonderzeichen einfach ein \ voranzustellen.

$text="Wir kaufen \{n\} Autos";
$map=new Alvine\Types\Map();
$map['n']='5';
$f=new \Alvine\Text\MessageFormatter($map);
echo (string) $f->format($text);
// ⇢ Wir kaufen {n} Autos

Die Standardbegrenzer { und } können durch beliebige Zeichenketten ersetzt werden. Im folgenden Beispiel werden diese durch START und END ersetzt. Auch hier funktioniert das maskieren der Begrenzer.

$map=new \Alvine\Types\Map(['fahren'=>'ok', 'object'=>'Auto']);

$m=new \Alvine\Text\MessageFormatter($map);

// Marker setzen
$m->setMarker('START', 'END');

echo $m->format('Wir \STARTfahren\END mit dem STARTobjectEND'); 
// ⇢ Wir STARTfahrenEND mit dem Auto

Eine Besonderheit ist die Möglichkeit Funktionen zu definieren. Dadurch lassen sich sehr komfortabel Ersetzungen durchführen.

$map=new \Alvine\Types\Map();
$m=new \Alvine\Text\MessageFormatter($map);

// Der Methodenname ist Case-Sesitive. $m->ondiph würde nicht aufgerufen.
$m->onDiph=function($obj, $value) {
    return "Auto";
};

echo (string) $m->format('{Diph}haus.'); 
// ⇢ Autohaus.

Wie im folgendem Beispiel dokumentiert, lassen sich Ersetzungen auch rekursiv verwenden.

$text="Wir kaufen {n} Autos";
$map=new Alvine\Types\Map();
$map['n']='{a}'; // erste Ersetzung ⇢ Wir kaufen {a} Autos
$map['a']='4';   // zweite Ersetzung ⇢ Wir kaufen 4 Autos
$f=new \Alvine\Text\MessageFormatter($map);
echo (string) $f->format($text);

Argumentzeichenkette

Mit den Klassen ArgumentText und MessageArgumentFormatter lassen sich Texte mit integrierter Map anlegen, verwalten und übersetzen.

Eine Argumentzeichenkette hat folgenden Aufbau:

Beschreibungstext mit Platzhaltern::key1=value1::key2=value2
─────────⯆───────────────────────╯
           Textzeile             ╰─⯆─╯           ╰──⯆───╯
                         Trennzeichen ╰─⯆────╯    Zweites
                                       Erstes      Schlüssel/
                                      Schlüssel/   Wert-Paar
                                       Wert-Paar

Über den Schlüssel pluralrulekey kann in der Klasse MessageArgumentFormatter spezifiziert werden, welcher Schlüssel für die Mehrzahlregel verwendet werden soll. Der Schlüssel wird aber nur dann verwendet, wenn in der format Methode kein $pluralRuleKey übergeben wird.

MessageArgumentFormatter

Die Klasse MessageArgumentFormatter erlaubt es schnell, einfach und sicher eine Argumentzeichenkette zusammenzustellen. Diese kann dann zum Beispiel im Zusammenhang mit Localen oder Exceptions verwendet werden.

$text = (new \Alvine\Text\ArgumentText('Das ist ein Test'))->setValue('a','b');
echo (string) $text;
// ⇢ Das ist ein Test::a=b

ArgumentText

Außerdem bietet die Klasse ArgumentText die Vorlage für die Verwendung mit dem MessageArgumentFormatter. In diesem Beispiel wird eine Nachricht erstellt und in der Zeichenkette die Argumente mitgegeben.

$text=(new \Alvine\Text\ArgumentText('Meine {key} Meldung'))
    ->setValue('key', 'erste');

echo (string) $text;
// ⇢ Meine {key} Meldung::key=erste

echo (new \Alvine\Text\MessageArgumentFormatter())->format($text);
// ⇢ Meine erste Meldung

Ein wichtiges Einsatzgebiet sind Fehlermeldungen. Hier können die Meldungen mit den Argumenten gespeichert werden.

try {
    // Fehler tritt auf
    throw new Exception('Kein freier Speicherplatz');
} catch(\Exception $ex) {
    $message="Es ist der Fehler '{error}' aufgetreten.::error=".$ex->getMessage();
}

echo (new \Alvine\Text\MessageArgumentFormatter())->format($message);
// ⇢ Es ist der Fehler 'Kein freier Speicherplatz' aufgetreten.

Objektpersitenz

Für die Suche nach Objekten erlaubt es ein Index schnell auf die gesuchten Objekte zuzugreifen. Die Erstellung des Index für serialisierte Objekte ist allerdings problematisch. Aus diesem Grund arbeiten viele Frameworks beim Speichern von Objekten mit ORM. Hierbei werden Eigenschaften auf Spalten gemappt. Alvine geht hier einen anderen Weg und erlaubt das anlegen von externen Indexen für die Suche. Im wesentlichen beruht das Konzept auf einem Objektspeicher und einer optimierten Suche.

$uri = new \Alvine\Net\Resource\URI('mongodb://127.0.0.1:27017/object/collection');
$provider = new Alvine\Persistence\Provider\MongoDB\DataObject($uri);
$storage = new \Alvine\Persistence\ObjectStorage($provider);
 
// Manager ist Singlton
$manager = \Alvine\Persistence\Manager::getInstance();
$manager->registerDefaultStorage($storage);

Ablauf einer Indexierung

Ein Find\Observer Plugin kann per attachObserver() an ein DataObject gehängt werden. Ändert das DataObject den Wert in der Datenbank wird die update() Methode des find\Observers aufgerufen. Dieser wiederum trägt die Änderung in den Index ein.

Zur Vorbereitung muss in der MySQL-Datenbank eine Tabelle angelegt werden.

Note

Für diese Beispiele müssen die entsprechenden Provider-Komponenten (Phar-Archive) geladen werden.

CREATE TABLE IF NOT EXISTS `alvine` ( 
`sys_ID` varchar(255) NOT NULL COMMENT 'ID des Objektes, bei Alvine-Objekten ist dies eine UUID', 
`sys_so` text NOT NULL COMMENT 'Serialisierte Objektdaten',
`sys_creation` datetime NOT NULL COMMENT 'Dieses Objekt wurde zu diesem Zeitpunkt erstellt', 
`sys_lastupdate` datetime NOT NULL COMMENT 'Die letzte änderung am Objekt erfolgte zu dieser Zeit',PRIMARY KEY (`sys_ID`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Speicher für Alvine-Objekte'

Note

Der Name der Tabelle kann frei gewählt werden und muss mit der URI an den Provider übergeben werden.

Siehe auch Namespace Alvine\Persistence\Provider\MySQL

Objekt anlegen

Ein soll ein Datenobjekt mittels den Werten SKU oder Artikelname gefunden werden.

/**
 * Indizierbares Produkt
 */
class IndexableItem extends \Alvine\Types\Map\SimpleMap implements Alvine\Persistence\Find\Indexable {

    protected $itemName=null;
    protected $sku=null;
    protected $price=null;

    /**
     * Neues Produkt erstellen
     */
    public function __construct($sku, $itemName, $price) {
        parent::__construct();
        $this->itemName=$itemName;
        $this->sku=$sku;
        $this->price=$price;
    }

    /**
     * Neues IndexDokument mit allen Indexwerten erstellen
     */
    public function getIndexDocument() {
        return (new Alvine\Persistence\Find\IndexDocument('IndexableItem', $this->getID()))->setValue('itemName', $this->itemName)
                ->setValue('sku', $this->sku)
                ->setValue('price', $this->price);
    }

}

/**
 * Objektspeicher zuweisen
 * Hier eine MySQL-Datenbank.
 */
$dataObject=new \Alvine\Persistence\Provider\MySQL\DataObject(new Alvine\Net\Resource\URI('mysql://user:password@localhost/alvine2/test'));
$storage=new \Alvine\Persistence\ObjectStorage($dataObject);

/**
 * Der Indexer sorgt für
 * das Updaate des Index.
 *
 * Über die IndexMap können unterschiedliche
 * Feldnamen gemappt werden.
 */
$indexer=new \Alvine\Persistence\Provider\Solr\IndexObserver(new Alvine\Net\Resource\URI('http://localhost:8983/solr/alvine-objectindex'), (new \Alvine\Persistence\Find\IndexMap())
        ->setValue('itemName', 'string_itemName')
        ->setValue('sku', 'string_sku')
        ->setValue('price', 'string_price'));

/**
 * Index als Beobachter in der Storage-Engine eintragen
 */
$storage->attachObserver($indexer);

/**
 * Objekt erstellen und schreiben
 */
$item=new IndexableItem('1425', 'Baseball Mütze', '14.25');
$storage->writeObject($item);

Im Objektspeicher - in diesem Fall eine MySQL-Datenbank - findet man ein Eintrag mit dem neue Objekt.

Objekt suchen

Will man die gespeicherten Produkte wiederfinden, so bedient man sich des Solr-Index.

Über eine Solr-Abfrage über das Feld sys_id kann man das Objekt aus der Datenbank in Solr leicht finden. Es können auch mittels fq=string_price%3A14.25 alle Produkte mit einem Preis von 14.25 abgerufen werden.

/*
 * Neues Finder Objekt erstellen
 * Übergabe einer SOLR URI
 *
 */
$finder = new \Alvine\Persistence\Provider\Solr\IndexFinder(new Alvine\Net\Resource\URI('http://127.0.0.1:8983/solr/alvine-objectindex'));
  
/*
 * Die Abfrage an die Datenbank
 * In diesem Beispiel wird der Wert "string_sku" mit dem Wert "1428" gesucht
 *
 */
$result = $finder->find('string_sku:1428');

Integrierte Objekte speichern

Speichert man integrierte Objekte, so entstehen zum einen Duplikate und zum anderen sind die gespeicherten Objekte sehr groß. Im folgenden Beispiel würde $obj sowohl das Objekt A, als auch das Objekt B enthalten.

class A extends \Alvine\Core\Alvine {

    public $sub;

}

class B extends \Alvine\Core\Alvine {

    public $value;

}

$subobject=new B();
$subobject->value='my object value';

// Objekt 1
$obj1=new A();
$obj1->sub=$subobject;

// Objekt 2
$obj2=new A();
$obj2->sub=$subobject;

Obwohl B von Objekt 1 und Objekt 2 identisch ist, würde das Objekt B bei der Verwendung des ObjektStorage zweimal abgespeichert. Will man zudem den Wert B::value ändern, müsste man beide Objekte laden und den Wert in beiden Objekten ändern.

Setzt man vor die Eigenschaft den Prefix associated, so wird beim speichern und laden des Objektes nur eine Kopie des Objekts im Storage gespeichert.

class A extends \Alvine\Core\Alvine {

    public $associatedSub;

}

class B extends \Alvine\Core\Alvine {

    public $value;

}

$subobject=new B();
$subobject->value='my object value';


// Objekt 1
$obj1=new A();
$obj1->associatedSub=$subobject;


// Objekt 2
$obj2=new A();
$obj2->associatedSub=$subobject;

Im zweiten Beispiel werden nicht zwei, sondern drei Objekte in die Datenbank geschrieben.

Berechtigungen

Um Datensätze nur einer bestimmten Entity (Benutzer, Gruppen oder Rollen) zugänglich zu machen, kann man Berechtigungen auf Record-Ebene vergeben. Dazu muss das zu speichernde Objekt das \Alvine\Security\Authorization\AccessControlList-Interface implementieren.

::uml::

skinparam monochrome true skinparam shadowing false

:user: –> (Objekt schreiben\ncreate/update) :user: –> (Objekt lesen) :user: –> (Objekt loeschen)

::end-uml::

Mit dem \Alvine\Security\Authorization\AccessControlListImplementation Trait steht hierfür bereits eine fertige Implementierung zur Verfügung.

Klassenstruktur

Benutzer, Gruppen und Rollen

Alle Identitäten sind von dem zentralen Interface \Alvine\Security\Authentication\Entity abgeleitet. Einer Entität können über eine Zugriffsliste verschiedene Rechte zugeordnet werden.

Ist kein Benutzer bekannt oder ist der Benutzer noch nicht identifiziert, so kommt das \Alvine\Security\Authentication\Anonymous Objekt zum Einsatz. Nach der Anmeldung wird der Benutzer zum authentifizierten Benutzer und wird über ein \Alvine\Security\Authentication\User Objekt abgebildet.

::uml::

skinparam monochrome true skinparam shadowing false

interface Subject interface Entity

Entity <|– Subject

Alvine <|– Identity Subject <|– Identity Identity <|– User

User <|– Anonymous User <|– Rightless

Alvine <|– Group Entity <|– Group Group <|– Role

Group “1” – “” User : contains

::end-uml::

Zugriffsrechte

Rechte werden ebsonso wie Entitäten durch Klassen ausgedrückt. Im Bereich der Persitenz sind die Rechte \Alvine\Persistence\Permission\Write, \Alvine\Persistence\Permission\Read und \Alvine\Persistence\Permission\Delete definiert.

::uml::

skinparam monochrome true skinparam shadowing false

interface Permission

Permission <|– DefaultPermission Alvine <|– DefaultPermission

DefaultPermission <|– Write DefaultPermission <|– Read DefaultPermission <|– Delete

::end-uml::

Jedes dieser Rechte steht für eine Operation. Benutzer die nur über das Recht Read verfügen, können das Objekt nicht ändern oder löschen.

AccessControlList

Zugriffsrechte und Entitäten werden in einer AccessControlList zusammengefasst. Das Interface \Alvine\Security\Authorization\AccessControlList definiert die notwenidigen Funktionen und mit dem \Alvine\Security\Authorization\AccessControlListImplementation Trait steht eine fertige Implementierung zur Verfügung.

::uml::

skinparam monochrome true skinparam shadowing false

interface AccessControl interface AccessControlList abstract AccessControlListImplementation <> interface Context

DefaultAccessControlList –|> AccessControlListImplementation Alvine <|– DefaultAccessControl AccessControl <|– DefaultAccessControl

Alvine <|– DefaultAccessControlList AccessControlList <|– DefaultAccessControlList

::end-uml::

In dem folgenden Beispiel wird eine Zugriffsliste definiert. Hier sieht man, dass der der Benutzer $user alle drei Rechte hat. $object ist in diesem Fall das zu speichernde Datenobjekt.

// Zugriffsrecte auf ein Objekt
$accessControl=new \Alvine\Security\Authorization\DefaultAccessControl();
// User darf lesen, schreiben und löschen
$accessControl->addEntity($user);

// Wenn diese Rechte eingeschränkt werden, wird weiter unten eine Exception geworfen.
$accessControl->addPermission(new Alvine\Persistence\Permission\Read());
$accessControl->addPermission(new Alvine\Persistence\Permission\Write());
$accessControl->addPermission(new Alvine\Persistence\Permission\Delete());

// Zugriffskontrolle in $object setzen
$object->addAccessControl($accessControl);

Note

In der Standard-Implementierung der Zugriffsliste AccessControlListImplementation wird die Berechtigung über eine Assoziation in einem eigenen Objekt und nicht direkt im Datenobjekt gespeichert. Damit werden pro Speichervorgang zwei Objekte geschrieben bzw. geladen. Je nach Konfiguration können die Objekte aus unterschiedlichen Storages kommen.

Beispiel

Das folgende Beispiel zeigt eine Implementierung der Zugriffsrechte.

/**
 * Stringklasse mit Zugangsberechtigung
 */
class MyString extends Alvine\Types\StringType implements \Alvine\Security\Authorization\AccessControlList {

    /**
     * Standardimplementierung des \Alvine\Security\Authorization\AccessControlList 
     * Interfaces für die Zugangsberechtigung.
     */
    use \Alvine\Security\Authorization\AccessControlListImplementation;

    public function __construct($value=null) {
        parent::__construct($value);
        $this->initAccessControlList();
    }

}

// Datenprovider & Storage-Objekt erstellen
// $mongoURL = 'mongodb://mongo.example.com/test/collection';
$provider=new Alvine\Persistence\Provider\MongoDB\DataObject(new Alvine\Net\Resource\URI($mongoURL));
$storage=new \Alvine\Persistence\ObjectStorage($provider);


// Handler mit SecurityContext ausstatten, da sonst der 
// Manager davon ausgeht, dass der Handler keine Sicherheit
// unterstützt.
// Das kann mit einem Anonymen Benutzer erfolgen, da der
// Manager das später überschreibt.
$context=new Alvine\Persistence\SecurityContext(new \Alvine\Security\Authentication\Anonymous);
$storage->setSecurityContext($context);

// Der Manager sorgt für die Verwaltung der Objekte
// Der Manager kann auch über eine Eigenschaftsdatei initialisiert werden
// $manager->initPersitenceFromProperties()
$manager=\Alvine\Persistence\Manager::getInstance();
$manager->registerDefaultStorage($storage);

// Aktueller User
$user=new Alvine\Security\Authentication\User('me', '1425-0001');



// Der Manager muss über die entsprechende Identität verfügen.
// Damit kann er in den Storage-Objekten den SecurityIndex setzen.
$manager->setIdentity($user);

// Das eigentliche Datenobjekt
$string=new MyString();

// Zugriffsrecte auf dieses Objekt
$accessControl=new \Alvine\Security\Authorization\DefaultAccessControl();
// User darf lesen, schreiben und löschen
$accessControl->addEntity($user);

// Wenn diese Rechte eingeschränkt werden, wird weiter unten eine Exception geworfen.
$accessControl->addPermission(new Alvine\Persistence\Permission\Read());
$accessControl->addPermission(new Alvine\Persistence\Permission\Write());
$accessControl->addPermission(new Alvine\Persistence\Permission\Delete());

// Zugriffskontrolle setzen
$string->addAccessControl($accessControl);

$id=$string->getID();

// ID ausgeben
echo $id.Alvine\Types\Character::CRLF;

// Inhalt setzen
$string->string='Ich esse gerne Bananen!';
//
// Objekt in das Default-Objekt schreiben
try {
    $result=$manager->writeObject($string);
} catch(Alvine\Persistence\Permission\ForbiddenException $ex) {
    // Fallback, wird hier nicht aufgerufen.
    echo "write not allowed".Alvine\Types\Character::CRLF;
    exit(1);
}

// ... und im Anschluß Objekt im Speicher löschen
unset($string);

// Abfrage im Kontext eines Annoymen Benutzers.
$manager->setIdentity(new \Alvine\Security\Authentication\Anonymous());

// Objekt mit Anonymus versuchen zu laden
// sollte \Alvine\Persistence\Permission\ReadForbiddenException
// werfen.
try {
    $obj=$manager->getObject(MyString::class, $id);
} catch(\Alvine\Persistence\ObjectNotFoundException $ex) {
    echo "object not found".Alvine\Types\Character::CRLF;
    exit(1);
} catch(\Alvine\Persistence\Permission\ReadForbiddenException $ex) {
    echo "read not allowed".Alvine\Types\Character::CRLF;
    // -> read not allowed
}


// Zweiter Versuch mit dem User der das Objekt geschrieben hat
$manager->setIdentity($user);

try {
    $obj=$manager->getObject(MyString::class, $id);
} catch(\Alvine\Persistence\ObjectNotFoundException $ex) {
    echo "object not found".Alvine\Types\Character::CRLF;
    exit(1);
} catch(\Alvine\Persistence\Permission\ReadForbiddenException $ex) {
    echo "read not allowed".Alvine\Types\Character::CRLF;
    exit(1);
}

// ... und ausgeben der Zeichenkette
echo (string) $obj.Alvine\Types\Character::CRLF;
// -> Ich esse gerne Bananen!


// Objekt löschen
try {
    $manager->deleteObject($obj);
} catch(\Alvine\Persistence\Permission\ForbiddenException $ex) {
    
}

Manager

Der Storage-Manager kümmert sich um das Lesen, Schreiben, Löschen und Auffinden von Objekten in den definierten Datenspeichern. Er verwaltet die Konfigurationen der Datenspeicher und routet die Anfragen der Geschäftslogig an die entsprechenden Storage-Provider weiter.

Der Manager greift dabei auf zwei unterschiedliche System zu. Zum einen den Objektspeicher und zum anderen einen Indexer. In klassischen relationalen Datenbanksystemen sind diese beiden Einheiten in einem Produkt zusammengefasst.

Note

Das Designziel von Alvine ist die beste Lösung für den entsprechenden Anwendungsfall einzusetzen.

Objektspeicher

Die Objektspeicher definieren, welche Klasse in welchem System abgespeichert werden sollen. So kann für jede Klasse die optimale Strategie gewählt werden. Session-Daten lassen sich gut in Redis speichern und für Bilddaten ist Riak eine gute Wahl.

Der Standardspeicher ist das Dateisystem. Für jedes Objekt wird eine temporäre Datei im Verzeichnis /alvine/cache/ im temporäre Verzeichnis des Systems angelegt.

Note

Aus Performancegründen, sollte dieses Verhalten in der Konfiguration oder der abgeleiteten Klasse auf ein performanteres System umgestellt werden.

Indexierung

Der Zugriff auf den Objektspeicher erfolgt ausnamslos über die UUID1 des Objektes. Aus diesem Grund muss für die Suche nach Objekten ein gesondertes System betrieben werden. Hier kann ein Suchlösung wie Solr oder ElasticSearch zum Einsatz kommen.

1

Ein Universally Unique Identifier (UUID) ist ein Standard für Identifikatoren.

Konfiguration

Die Konfiguration des \Alvine\Persistence\Manager kann über Eigenschaften (Properties) erfolgen. Ein Beispiel kann folgendermaßen aussehen:

# DEFAULT-Definition f\u00fcr die Speicherung von Objektekten
application.persistence.default.class=\Alvine\Persistence\Provider\MongoDB\DataObject
application.persistence.default.uri={ENV:MONGODB_URI}

# Index
application.storageindex.default.class=\Alvine\Persistence\Provider\Solr\IndexObserver
application.storageindex.default.finder=\Alvine\Persistence\Provider\Solr\IndexFinder
application.storageindex.default.uri={ENV:SOLR_URI}

Jeder Handler (Storage und Indexer) besitzen noch eigene Eigenschaften zur Definition von Zugangsdaten, Pfaden und weiteren Optionen. Diese können den entsprechenden Komponenten entnommen werden.

Die Makros {ENV:SOLR_URI} und {ENV:MONGODB_URI} werden dabei durch die Werte der Environmentvariabeln SOLR_URI und MONGODB_URI ersetzt.

Objektspeicher

EigenschaftBeschreibungBeispiel
application.persistence.default.referenceName der Klasse, für die diese Regelung gilt\Alvine\Security\Authentication\User
application.persistence.default.className der Klasse, die für das Speichern des Objektes zu verwenden ist.\Alvine\Persistence\Provider\File\DataObject

Indexierung

EigenschaftBeschreibungBeispiel
application.storageindex.default.className der Klasse, die die Indexierung übernimmt\Alvine\Persistence\Provider\Solr\IndexObserver
application.storageindex.default.finderName der Klasse, die für die Suche zuständig ist\Alvine\Persistence\Provider\Solr\IndexFinder

Relationale Datenbanken

Der Zugriff auf relationale Datenbanken ist eine Kernfunktion von Webanwendungen. Mit Hilfe des Frameworks wird ein für das Framework optimierter Zugriff auf das darunterliegende PDO gewährt.

Für die folgenden Beispiele ist eine gesonderte Tabelle in einer Datenbank notwendig. In dieser Datenbank muss die Tabelle example-1 angelegt werden. Diese Tabelle kann über die folgenden SQL-Anweisungen in einer relationalen Datenbank wie MySQL oder SQLite angelegt werden.

Beispieldatenbank anlegen

CREATE TABLE `example-1` (
  `ISBN` varchar(17) COLLATE utf8mb4_unicode_ci 
       NOT NULL COMMENT '13 stellige ISBN Nummer des Buches',
  `title` varchar(45) COLLATE utf8mb4_unicode_ci 
       DEFAULT NULL COMMENT 'Titel des Buches',
  `record` datetime DEFAULT NULL 
       COMMENT 'Datum der Anlage des Datensatzes',
  `book-count` int(11) DEFAULT NULL 
       COMMENT 'Anzahl der Bücher dieser ISDN im Lager',
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`ID`),
  UNIQUE KEY `ISBN_UNIQUE` (`ISBN`)
) ENGINE=InnoDB DEFAULT 
  CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci 
  COMMENT='Testdatenbank für Dokumentation';


INSERT INTO `example-1` 
            (`ISBN`, `title`, `record`, `book-count`) 
     VALUES ('978-3-86680-192-9', 'Stadt und Landkreis Hof', 
            '2016-12-12 00:00:00', '99');

PVP-Code:

/**
 * Datenquelle/DataSource erstellen
 */
$dsn=new \Alvine\Persistence\Provider\MySQL\DataSource
    ($host, $port, $database, $table, $user, $password, $parameter);
/**
 * Datenobjekt mit der Verbindung zum Datenbankserver
 */
$dataObject=new \Alvine\Persistence\Relation\SQL\DataObject
    ($dsn, new \Alvine\Types\Map\SimpleMap());

/**
 * Ausführen des Queries
 */
$result=$dataObject->execute(
    "SELECT * FROM `example-1` ".
    " WHERE `ISBN`='978-3-86680-192-9'");

/**
 * Es wird folgende SQL-Anweisung auf dem Server ausgeführt:
 * SELECT * FROM `example-1` WHERE `ISBN`='978-3-86680-192-9'
 */
if($result->containErrors()) {
    echo "FEHLER\r\n";
    echo (string) $result->getErrors();
    exit(1);
}

/**
 * Das Ergebnis des Queries ist eine Collection der einzelnen Ergebnisse.
 */
foreach($result AS $resultSet) {
    $data=$resultSet->getData();
    print_r($data);

    /**
     * Dies ergibt folgende Ausgabe
     * 
     * Array
     * (
     *     [0] => Array
     *         (
     *             [ISBN] => 978-3-86680-192-9
     *             [title] => Stadt und Landkreis Hof
     *             [record] => 2016-12-12 00:00:00
     *             [book-count] => 99
     *             [ID] => 1
     *         )
     * 
     * )
     */
}


/** EXAMPLE:END * */
echo "\n".$profiler->record('end')->getDuration();

SELECT

Ausführen eines Queries

Die Abfrage über ein einfaches Statements hat den Vorteile der guten Lesbarkeit. Das statement wird als Zeichenkette direkt an die Datenbank übergeben. Für einfache Abfragen ist dieser Ansatz schnell und leicht umzusetzen.

/**
 * Datenquelle/DataSource erstellen
 */
$dsn=new \Alvine\Persistence\Provider\MySQL\DataSource
    ($host, $port, $database, $table, $user, $password, $parameter);
/**
 * Datenobjekt mit der Verbindung zum Datenbankserver
 */
$dataObject=new \Alvine\Persistence\Relation\DataObject
    ($dsn, new \Alvine\Types\Map\SimpleMap());

/**
 * Ausführen des Queries
 */
$result=$dataObject->execute(
    "SELECT * FROM `example-1` ".
    " WHERE `ISBN`='978-3-86680-192-9'");

/**
 * Es wird folgende SQL-Anweisung auf dem Server ausgeführt:
 * SELECT * FROM `example-1` WHERE `ISBN`='978-3-86680-192-9'
 */
if($result->containErrors()) {
    echo "FEHLER\r\n";
    echo (string) $result->getErrors();
    exit(1);
}

/**
 * Das Ergebnis des Queries ist eine Collection der einzelnen Ergebnisse.
 */
foreach($result AS $resultSet) {
    $data=$resultSet->getData();
    print_r($data);

    /**
     * Dies ergibt folgende Ausgabe
     * 
     * Array
     * (
     *     [0] => Array
     *         (
     *             [ISBN] => 978-3-86680-192-9
     *             [title] => Stadt und Landkreis Hof
     *             [record] => 2016-12-12 00:00:00
     *             [book-count] => 99
     *             [ID] => 1
     *         )
     * 
     * )
     */
}

Definiertes Select

Die Abfrage über definierte Statements - im Gegensatz zu den einfachen Statements - hat mehrere Vorteile. Zum einen können die einzelnen Felder über Objekte abgebildet werden und zum anderen werden diese Abfragen mittels Prepared Statements an die Datenbank gesendet. Dadurch wird die Gefahr von SQL-Injections gemildert.

/**
 * Datenquelle/DataSource erstellen
 */
$dsn=new \Alvine\Persistence\Provider\MySQL\DataSource
    ($host, $port, $database, $table, $user, $password, $parameter);
/**
 * Datenobjekt mit der Verbindung zum Datenbankserver
 */
$dataObject=new \Alvine\Persistence\Relation\DataObject
    ($dsn, new \Alvine\Types\Map\SimpleMap());

$definition=new \Alvine\Persistence\Relation\Definition();

$indexField=new \Alvine\Persistence\Relation\Field\Varchar('example-1', 'ISBN');
$titleField=new \Alvine\Persistence\Relation\Field\Varchar('example-1', 'title');

/** 
 * Über einen LeseHook, kann der Wert des Feldes 
 * nach dem Lesen bearbeitet werden 
 */
$titleField->addReadHook(function($value) {
    return \str_pad($value, 45, '.');
});

$definition->append($indexField);
$definition->append($titleField);
$definition->append(new \Alvine\Persistence\Relation\Field\Integer
    ('example-1', 'book-count'));
$definition->append(new \Alvine\Persistence\Relation\Field\Date
    ('example-1', 'record'));

/**
 * Ein Definiertes Statement bietet zum einen eine 
 * höhere Sicherheit und zum anderen eine optimierte Performance.
 */
$statement=new \Alvine\Persistence\Relation\SQL\Select\DefinedStatement
    ($definition);

/**
 * Where-Abfrage definieren
 */
$statement->where(new \Alvine\Persistence\Relation\SQL\Where
    ($indexField));

/**
 * Abfragen lassen sich gruppieren
 */
$statement->groupBy(new \Alvine\Persistence\Relation\SQL\GroupBy
    ($indexField));

/**
 * Auch Limits lassen sich über ein Objekt definieren
 */
$statement->limit(new \Alvine\Persistence\Relation\SQL\Limit(0, 1));

/**
 * Für das WHERE-Statement müssen 
 * Werte zugewiesen werden.
 */
$records=new \Alvine\Persistence\Relation\Records();
$records->append(
    new \Alvine\Persistence\Relation\Record(
        ['ISBN'=>$searchISBN]));

$query=new \Alvine\Persistence\Relation\Query
    ($statement, $records);

/**
 * Ausführen des Queries
 */
$result=$dataObject->execute($query);
/**
 * Es wird folgende SQL-Anweisung auf dem Server ausgeführt:
 * SELECT `example-1`.`ISBN`,`example-1`.`title`,
 *        `example-1`.`book-count`,
 *        `example-1`.`record` FROM `example-1` 
 * WHERE `example-1`.`ISBN`='978-3-86680-192-9' 
 * GROUP BY `example-1`.`ISBN` 
 * LIMIT 0,1
 */

if($result->containErrors()) {
    echo "FEHLER\r\n";
    echo (string) $result->getErrors();
    exit(1);
}

/**
 * Das Ergebnis des Queries ist eine Collection.
 */
foreach($result AS $resultSet) {
    $data=$resultSet->getData();
    print_r($data);

    /**
     * Dies ergibt folgende Ausgabe
     * 
     * [0] => Array
     *   (
     *       [ISBN] => 978-3-86680-192-9
     *       [title] => Stadt und Landkreis Hof......................
     *       [book-count] => 99
     *       [record] => Alvine\Date\DateTime Object
     *           (
     *               [nanosecond:protected] => 
     *               [second:protected] => 0
     *               [minute:protected] => 0
     *               [hour:protected] => 0
     *               [day:protected] => 12
     *               [month:protected] => 12
     *               [year:protected] => 2016
     *               [mask:protected] => 126
     *               [properties:protected] => Array
     *                   (
     *                   )
     *
     *               [hash:protected] => 
     *           )
     *
     *   )
     */
}

5e05bd59-84b7-48da-a9d4-070e9441fd81

/**
 * Datenquelle/DataSource erstellen
 */
$dsn=new \Alvine\Persistence\Provider\MySQL\DataSource
    ($host, $port, $database, $table, $user, $password, $parameter);
/**
 * Datenobjekt mit der Verbindung zum Datenbankserver
 */
$dataObject=new \Alvine\Persistence\Relation\SQL\DataObject
    ($dsn, new \Alvine\Types\Map\SimpleMap());

$data=[
    ['example-1', 'ISBN'],
    ['example-1', 'title'],
    ['example-1', 'record', 
        '\Alvine\Persistence\Relation\Field\Date'],
    ['example-1', 'book-count', 
        '\Alvine\Persistence\Relation\Field\Integer'],
];

$definition=\Alvine\Persistence\Relation\Definition::getInstanceFromArray($data);

/**
 * Ein Definiertes Statement bietet zum einen eine höhere Sicherheit 
 * und zum anderen eine optimierte Performance.
 */
$statement=new \Alvine\Persistence\Relation\SQL\Select\DefinedStatement
    (\Alvine\Persistence\Relation\Definition::getInstanceFromArray($data));

/**
 * Where-Abfrage definieren und das erste Feld aus
 * der Definition verwenden
 */
$statement->where(new \Alvine\Persistence\Relation\SQL\Where
    ($definition->rewind()));

/**
 * Auch Limits lassen sich über ein Objekt definieren
 */
$statement->limit(1);

/**
 * Für das WHERE-Statement müssen 
 * Werte zugewiesen werden.
 */
$records=new \Alvine\Persistence\Relation\Records();
$records->append(new \Alvine\Persistence\Relation\Record(['ISBN'=>$searchISBN]));

$query = new \Alvine\Persistence\Relation\Query($statement, $records);

/**
 * Ausführen des Queries
 */
$result=$dataObject->execute($query);
/**
 * Es wird folgende SQL-Anweisung auf dem Server ausgeführt:
 * SELECT `example-1`.`ISBN`,`example-1`.`title`,`example-1`.`record`,
 *        `example-1`.`book-count` 
 * FROM   `example-1` 
 * WHERE  `example-1`.`ISBN`='978-3-86680-192-9' 
 * LIMIT  1
 */

if($result->containErrors()) {
    echo "FEHLER\r\n";
    echo (string) $result->getErrors();
    exit(1);
}

/**
 * Das Ergebnis des Queries ist eine Collection.
 */
foreach($result AS $resultSet) {
    $data=$resultSet->getData();
    print_r($data);

    /**
     * Dies ergibt folgende Ausgabe
     * 
     * [0] => Array
     *   (
     *       [ISBN] => 978-3-86680-192-9
     *       [title] => Stadt und Landkreis Hof......................
     *       [book-count] => 99
     *       [record] => Alvine\Date\DateTime Object
     *           (
     *               [nanosecond:protected] => 
     *               [second:protected] => 0
     *               [minute:protected] => 0
     *               [hour:protected] => 0
     *               [day:protected] => 12
     *               [month:protected] => 12
     *               [year:protected] => 2016
     *               [mask:protected] => 126
     *               [properties:protected] => Array
     *                   (
     *                   )
     *
     *               [hash:protected] => 
     *           )
     *
     *   )
     */
}


/** EXAMPLE:END * */
echo "\n".$profiler->record('end')->getDuration();

Definiertes Insert

Die Abfrage über definierte Statements hat mehrere Vorteile. Zum einen können die einzelnen Felder über Objekte abgebildet werden und zum anderen werden diese Abfragen mittels Prepared Statements an die Datenbank gesendet.

/**
 * Datenquelle/DataSource erstellen
 */
$dsn=new \Alvine\Persistence\Provider\MySQL\DataSource
    ($host, $port, $database, $table, $user, $password, $parameter);
/**
 * Datenobjekt mit der Verbindung zum Datenbankserver
 */
$dataObject=new \Alvine\Persistence\Relation\DataObject
    ($dsn, new \Alvine\Types\Map\SimpleMap());

/**
 * Felddefinition festlegen
 */
$definition=new \Alvine\Persistence\Relation\Definition();
$definition->append(
    new \Alvine\Persistence\Relation\Field\Varchar('example-1', 'ISBN'));
$definition->append(
    new \Alvine\Persistence\Relation\Field\Integer('example-1', 'book-count'));
$definition->append(
    new \Alvine\Persistence\Relation\Field\Varchar('example-1', 'title'));
$definition->append(
    new \Alvine\Persistence\Relation\Field\Date('example-1', 'record'));

/**
 * Ein definiertes Statement bietet zum einen eine höhere Sicherheit 
 * und zum anderen eine optimierte Performance bei Mehrfachanfragen.
 */
$statement=new \Alvine\Persistence\Relation\SQL\Insert\DefinedStatement
    ($definition);

/**
 * Datensätze
 */
$records=new \Alvine\Persistence\Relation\Records();

$records->append(new \Alvine\Persistence\Relation\Record(
    ['ISBN'=>'478-1-86681-192-6', 'book-count'=>'22',
    'title'=>'My Title 1', 'record'=>(string) 
        new \Alvine\Date\Date(2017, 12, 31)]));
$records->append(new \Alvine\Persistence\Relation\Record(
    ['ISBN'=>'478-2-86681-192-6', 'book-count'=>'33',
    'title'=>'My Title 1', 'record'=>(string) 
        new \Alvine\Date\Date(2017, 12, 31)]));


$query=new \Alvine\Persistence\Relation\Query($statement, $records);

/**
 * Ausführen des Queries
 */
try {
    $result=$dataObject->execute($query);
    /**
     * Es werden folgende SQL-Anweisungen auf dem Server ausgeführt:
     * INSERT INTO `example-1` (`ISBN`,`book-count`,`title`,`record`) 
     *      VALUES ('478-1-86681-192-6',22,'My Title 1','2017-12-31')
     * INSERT INTO `example-1` (`ISBN`,`book-count`,`title`,`record`) 
     *      VALUES ('478-2-86681-192-6',33,'My Title 1','2017-12-31')
     */
} catch(\Exception $e) {
    exit-1;
}

if($result->containErrors()) {
    echo "FEHLER\r\n";
    echo (string) $result->getErrors();
    exit(1);
}
/**
 * Das Ergebnis des Queries ist eine Collection.
 */
foreach($result AS $resultSet) {
    /** Gibt die eingefügte ID zurück */
    print_r($resultSet->getLastInsertID());
}

Definiertes Update

Die Abfrage über definierte Statements hat mehrere Vorteile. Zum einen können die einzelnen Felder über Objekte abgebildet werden und zum anderen werden diese Abfragen mittels Prepared Statements an die Datenbank gesendet.

/**
 * Datenquelle/DataSource erstellen
 */
$dsn=new \Alvine\Persistence\Provider\MySQL\DataSource
    ($host, $port, $database, $table, $user, $password, $parameter);
/**
 * Datenobjekt mit der Verbindung zum Datenbankserver
 */
$dataObject=new \Alvine\Persistence\Relation\DataObject
    ($dsn, new \Alvine\Types\Map\SimpleMap());

/**
 * Felddefinition festlegen
 */
$definition=new \Alvine\Persistence\Relation\Definition();
$definition->append(
    new \Alvine\Persistence\Relation\Field\Integer
    ('example-1', 'book-count'));

/**
 * Ein definiertes Statement bietet zum einen eine höhere Sicherheit 
 * und zum anderen eine optimierte Performance bei Mehrfachanfragen.
 */
$statement=new \Alvine\Persistence\Relation\SQL\Update\DefinedStatement
    ($definition);

/**
 * Where-Abfrage definieren
 */
$statement->where(new \Alvine\Persistence\Relation\SQL\Where
    (new \Alvine\Persistence\Relation\Field\Varchar
    ('example-1', 'ISBN')));

/**
 * Datensätze
 */
$records=new \Alvine\Persistence\Relation\Records();

$records->append(new \Alvine\Persistence\Relation\Record(
    ['ISBN'=>'478-1-86681-192-6', 'book-count'=>'199']));

$query=new \Alvine\Persistence\Relation\Query($statement, $records);

/**
 * Ausführen des Queries
 */
try {
    $result=$dataObject->execute($query);

    /**
     * Es werden folgende SQL-Anweisungen auf dem Server ausgeführt:
     * UPDATE `example-1` SET `book-count`=199 
     *  WHERE `example-1`.`ISBN`='478-1-86681-192-6'
     */
} catch(\Exception $e) {
    exit-1;
}

if($result->containErrors()) {
    echo "FEHLER\r\n";
    echo (string) $result->getErrors();
    exit(1);
}
/**
 * Das Ergebnis des Queries ist eine Collection.
 */
foreach($result AS $resultSet) {
    //print_r($resultSet);
    /** Gibt die Anzahl der betroffenen Datensätze zurückx */
    print_r($resultSet->getAffectedRows());
}

Definiertes Delete

Die Abfrage über definierte Statements hat mehrere Vorteile. Zum einen können die einzelnen Felder über Objekte abgebildet werden und zum anderen werden diese Abfragen mittels Prepared Statements an die Datenbank gesendet.

/**
 * Datenquelle/DataSource erstellen
 */
$dsn=new \Alvine\Persistence\Provider\MySQL\DataSource
    ($host, $port, $database, $table, $user, $password, $parameter);
/**
 * Datenobjekt mit der Verbindung zum Datenbankserver
 */
$dataObject=new \Alvine\Persistence\Relation\DataObject
    ($dsn, new \Alvine\Types\Map\SimpleMap());

$definition=new \Alvine\Persistence\Relation\Definition();

$indexField=new \Alvine\Persistence\Relation\Field\Varchar
    ('example-1', 'ISBN');

$definition->append($indexField);

/**
 * Ein Definiertes Statement bietet zum einen eine höhere Sicherheit 
 * und zum anderen eine optimierte Performance.
 */
$statement=new \Alvine\Persistence\Relation\SQL\Delete\DefinedStatement
    ($definition);

/**
 * Where-Abfrage definieren
 */
$statement->where(new \Alvine\Persistence\Relation\SQL\Where
    ($indexField));

/**
 * Datensätze
 */
$records=new \Alvine\Persistence\Relation\Records();
$records->append(new \Alvine\Persistence\Relation\Record(
    ['ISBN'=>'478-1-86681-192-6']));
$query=new \Alvine\Persistence\Relation\Query($statement, $records);

/**
 * Ausführen des Queries
 */
try {
    $result=$dataObject->execute($query);

    /**
     * Es wird folgende SQL-Anweisung auf dem Server ausgeführt:
     * DELETE FROM `example-1` WHERE `example-1`.`ISBN`='478-1-86681-192-6'
     */
} catch(\Exception $e) {
    print_r($e);
    exit-1;
}

if($result->containErrors()) {
    echo "FEHLER\r\n";
    echo (string) $result->getErrors();
    exit(1);
}
/**
 * Das Ergebnis des Queries ist eine Collection.
 */
foreach($result AS $resultSet) {
    /** Gelöschte Datensätze */
    print_r($resultSet->getAffectedRows());
}

Title: Transaktionen mit relationale Datenbanken und PDO

Transaktionen

Sollen mehrere Abfragen gebündelt an den Datenbankserver übertragen werden und sollen bei einem Fehler die Anweisugen nicht ausgeführt werden, so kommen Transaktionen zum Einsatz. In dem folgenden Beispiel wird die Verwendung von Transaktionen exemplarisch dargestellt.

Verschachtelte Transaktionen

Wird in einer Funktion eine Transaktion verwendet, so könnte man diese Funktion nicht in andere Transaktionen einbinden. Die Funktion doSomethingInTransaction() startet eine Transaktion und beendet diese auch wieder. Mit dem Commit wird aber auch die Transaktion von doOtherThingsInTransaction beendet.

/**
 * Datenquelle/DataSource erstellen
 */
$dsn=new \Alvine\Persistence\Provider\MySQL\DataSource
    ($host, $port, $database, $table, $user, $password, $parameter);
/**
 * Datenobjekt mit der Verbindung zum Datenbankserver
 */
$dataObject=new \Alvine\Persistence\Relation\DataObject
    ($dsn, new \Alvine\Types\Map\SimpleMap());

function doSomethingInTransaction(\Alvine\Persistence\Relation\DataObject $dataObject) {

    $dataObject->beginTransaction();

    // Einige SQL-Statements ...

    $dataObject->commitTransaction();
}

function doOtherThingsInTransaction(\Alvine\Persistence\Relation\DataObject $dataObject) {

    $dataObject->beginTransaction();
    doSomethingInTransaction($dataObject);

    // Die Transaktion des DataObjekt ist aber bereits beendet, 
    // da doSomethingInTransaction ein Commit ausführt.
    $dataObject->commitTransaction();
}

Dieses Verhalten ist aber nicht gewünscht.

Durch den Aufruf von DataObject::enableTransactionStackCounter() wird die Transaktion nun nicht mehr in der Funktion doSomethingInTransaction() beendet, sondern erst in doOtherThingsInTransaction.

Beispiel

/**
 * Datenquelle/DataSource erstellen
 */
$dsn=new \Alvine\Persistence\Provider\MySQL\DataSource
    ($host, $port, $database, $table, $user, $password, $parameter);
/**
 * Datenobjekt mit der Verbindung zum Datenbankserver
 */
$dataObject=new \Alvine\Persistence\Relation\DataObject
    ($dsn, new \Alvine\Types\Map\SimpleMap());


/*
 * Gebündelte Statements
 */
$transaction=new \Alvine\Persistence\Relation\Transaction(true);

/** Erstes Statement */
$records=new \Alvine\Persistence\Relation\Records();

$records->append(new \Alvine\Persistence\Relation\Record(
    ['ISBN'=>'111-1-86681-001-6', 'book-count'=>'10',
    'title'=>'My Title 10', 'record'=>(string) 
        new \Alvine\Date\Date(2017, 12, 31)]));
$records->append(new \Alvine\Persistence\Relation\Record(
    ['ISBN'=>'222-2-86681-001-6', 'book-count'=>'20',
    'title'=>'My Title 20', 'record'=>(string) 
        new \Alvine\Date\Date(2017, 12, 31)]));

$insertStatement=new \Alvine\Persistence\Relation\SQL\Insert\DefinedStatement(
    \Alvine\Persistence\Relation\Definition::getInstanceFromArray([
        ['example-1', 'ISBN'],
        ['example-1', 'title'],
        ['example-1', 'record', '\Alvine\Persistence\Relation\Field\Date'],
        ['example-1', 'book-count', '\Alvine\Persistence\Relation\Field\Integer'],
    ]));
$transaction->append(new \Alvine\Persistence\Relation\Query
    ($insertStatement, $records));

/** Zweites Statement */
$records=new \Alvine\Persistence\Relation\Records();

$records->append(new \Alvine\Persistence\Relation\Record(
    ['ISBN'=>'111-1-86681-001-6', 'book-count'=>'11']));
$records->append(new \Alvine\Persistence\Relation\Record(
    ['ISBN'=>'222-2-86681-001-6', 'book-count'=>'22']));

$updateStatement=new \Alvine\Persistence\Relation\SQL\Update\DefinedStatement(
    \Alvine\Persistence\Relation\Definition::getInstanceFromArray([
        ['example-1', 'book-count', '\Alvine\Persistence\Relation\Field\Integer']
    ]));
$updateStatement->where(new \Alvine\Persistence\Relation\SQL\Where
    (new \Alvine\Persistence\Relation\Field\Varchar
    ('example-1', 'ISBN')));
$transaction->append(new \Alvine\Persistence\Relation\Query(
    $updateStatement, $records));

/**
 * Ausführen des Queries
 */
try {
    $result=$dataObject->execute($transaction);
    /**
     * Es werden folgende SQL-Anweisungen auf dem Server ausgeführt:
     * START TRANSACTION
     * INSERT INTO `example-1` (`ISBN`,`title`,`record`,`book-count`) 
     *      VALUES ('111-1-86681-001-6','My Title 10','2017-12-31',10)
     * INSERT INTO `example-1` (`ISBN`,`title`,`record`,`book-count`) 
     *      VALUES ('222-2-86681-001-6','My Title 20','2017-12-31',20)
     * UPDATE `example-1` SET `book-count`=11 
     *  WHERE `example-1`.`ISBN`='111-1-86681-001-6'
     * UPDATE `example-1` SET `book-count`=22 
     *  WHERE `example-1`.`ISBN`='222-2-86681-001-6'
     * COMMIT
     */
} catch(\Exception $e) {
    exit-1;
}

if($result->containErrors()) {
    echo "FEHLER\r\n";
    echo (string) $result->getErrors();
    print_r($result);
    exit(1);
}
/**
 * Das Ergebnis des Queries ist eine Collection.
 */
foreach($result AS $resultSet) {
    //print_r($resultSet);
    /** Gibt die Anzahl der betroffenen Datensätze zurückx */
    print_r($resultSet);
}

Tritt ein Fehler auf oder ist ein Statement inkorrekt, so erfolgt wie im folgenden Beispiel ein Rollback.

<!--- example:persistence/relationaldatabase-example-transaction-rollback.php -->
```php
/**
 * Datenquelle/DataSource erstellen
 */
$dsn=new \Alvine\Persistence\Provider\MySQL\DataSource
    ($host, $port, $database, $table, $user, $password, $parameter);
/**
 * Datenobjekt mit der Verbindung zum Datenbankserver
 */
$dataObject=new \Alvine\Persistence\Relation\DataObject
    ($dsn, new \Alvine\Types\Map\SimpleMap());


/*
 * Gebündelte Statements
 */
$transaction=new \Alvine\Persistence\Relation\Transaction(true);

/** Erstes Statement */
$records=new \Alvine\Persistence\Relation\Records();

$records->append(new \Alvine\Persistence\Relation\Record(
    ['ISBN'=>'111-1-86681-001-6', 'book-count'=>'10',
    'title'=>'My Title 10', 'record'=>(string) 
        new \Alvine\Date\Date(2017, 12, 31)]));
$records->append(new \Alvine\Persistence\Relation\Record(
    ['ISBN'=>'222-2-86681-001-6', 'book-count'=>'20',
    'title'=>'My Title 20', 'record'=>(string) 
        new \Alvine\Date\Date(2017, 12, 31)]));

$insertStatement=new \Alvine\Persistence\Relation\SQL\Insert\DefinedStatement(
    \Alvine\Persistence\Relation\Definition::getInstanceFromArray([
        ['example-1', 'ISBN'],
        ['example-1', 'title'],
        ['example-1', 'record', 
            '\Alvine\Persistence\Relation\Field\Date'],
        ['example-1', 'book-count', 
            '\Alvine\Persistence\Relation\Field\Integer'],
    ]));
$transaction->append(new \Alvine\Persistence\Relation\Query
    ($insertStatement, $records));

/** Zweites Statement */
$records=new \Alvine\Persistence\Relation\Records();

$records->append(new \Alvine\Persistence\Relation\Record(
    ['ISBN'=>'111-1-86681-001-6', 'book-count'=>'11']));
$records->append(new \Alvine\Persistence\Relation\Record(
    ['ISBN'=>'222-2-86681-001-6', 'book-count'=>'22']));

$updateStatement=new \Alvine\Persistence\Relation\SQL\Update\DefinedStatement(
    \Alvine\Persistence\Relation\Definition::getInstanceFromArray([
        ['example-1', 'book-count', 
            '\Alvine\Persistence\Relation\Field\Integer']
    ]));
$updateStatement->where(new \Alvine\Persistence\Relation\SQL\Where
    (new \Alvine\Persistence\Relation\Field\Varchar
    ('example-1', 'ISBN')));
$transaction->append(new \Alvine\Persistence\Relation\Query
    ($updateStatement, $records));

$statement=new \Alvine\Persistence\Relation\GenericStatement('DO FEHLER');
$transaction->append(new \Alvine\Persistence\Relation\Query($statement));

/**
 * Ausführen des Queries
 */
try {
    $result=$dataObject->execute($transaction);
    /**
     * Es werden folgende SQL-Anweisungen auf dem Server ausgeführt:
     * START TRANSACTION
     * INSERT INTO `example-1` (`ISBN`,`title`,`record`,`book-count`) 
     *        VALUES ('111-1-86681-001-6','My Title 10','2017-12-31',10)
     * INSERT INTO `example-1` (`ISBN`,`title`,`record`,`book-count`) 
     *        VALUES ('222-2-86681-001-6','My Title 20','2017-12-31',20)
     * UPDATE `example-1` SET `book-count`=11 
     *  WHERE `example-1`.`ISBN`='111-1-86681-001-6'
     * UPDATE `example-1` SET `book-count`=22 
     *  WHERE `example-1`.`ISBN`='222-2-86681-001-6'
     * DO FEHLER
     * ROLLBACK
     */
} catch(\Exception $e) {
    exit-1;
}

if($result->containErrors()) {
    echo "FEHLER\r\n";
    echo (string) $result->getErrors();
    print_r($result);
    exit(1);
}
/**
 * Das Ergebnis des Queries ist eine Collection.
 */
foreach($result AS $resultSet) {
    //print_r($resultSet);
    /** Gibt die Anzahl der betroffenen Datensätze zurückx */
    print_r($resultSet);
}

Rollback

Verlorene“ Auto-Inkrement-Werte und Sequenzlücken

Wenn in allen Sperrmodi (0, 1 und 2) eine Transaktion, die Auto-Inkrement-Werte erzeugt hat, zurückgesetzt wird, sind diese Auto-Inkrement-Werte “verloren”. Sobald ein Wert für eine Spalte mit automatischer Erhöhung erzeugt wurde, kann er nicht mehr zurückgesetzt werden, unabhängig davon, ob die Anweisung “INSERT-like” abgeschlossen ist oder nicht und ob die enthaltene Transaktion zurückgesetzt wird oder nicht. Solche verlorenen Werte werden nicht wiederverwendet. Daher kann es zu Lücken in den Werten kommen, die in einer AUTO_INCREMENT-Spalte einer Tabelle gespeichert sind.

Quelle dev.mysql.com

Operationen & Funktionen

Operationen und Funktionen sind als Statement abgebildet und können somit an vielen Stellen zum Einsatz kommen.

Operationen

Operatoren erlauben die einfache Erstellung von SQL-Queries mit richtiger Darstellung der Werte und Operatoren. Die einzelnen Klassen erwarten je nach Operator ein, zwei oder mehrere Operanden. Operanden können unterschiedliche Typen haben.

$operation=new \Alvine\Persistence\Relation\SQL\Operation\AndOperation(
    true, true);

echo (string) $operation;
// -> TRUE AND TRUE

$operation=new \Alvine\Persistence\Relation\SQL\Operation\AndOperation(
    'A', true);

echo (string) $operation;
// -> 'A' AND TRUE

$operation=new \Alvine\Persistence\Relation\SQL\Operation\AndOperation(
    new \Alvine\Persistence\Relation\Field\Varchar('mytable', 'a', null, 'myalias'),
    new \Alvine\Persistence\Relation\Field\Varchar('mytable', 'b', null, 'myalias'),
    new \Alvine\Persistence\Relation\Field\Varchar('mytable', 'c', null, 'myalias'));

echo (string) $operation;
// ->`mytable`.`a` AND `mytable`.`b` AND `mytable`.`c`

Operationen lassen sich auch verschachteln.

$operationA=new \Alvine\Persistence\Relation\SQL\Operation\AndOperation(
    'A', 'B');

$operationB=new \Alvine\Persistence\Relation\SQL\Operation\AndOperation(
    'C', 'D', 'E');

$operation=new \Alvine\Persistence\Relation\SQL\Operation\OrOperation($operationA, $operationB);

echo (string) $operation;
// -> ('A' AND 'B') OR ('C' AND 'D' AND 'E')

Vergleiche können mit der Equal Funktion definiert werden.

$operation=new \Alvine\Persistence\Relation\SQL\Operation\OrOperation(
    new \Alvine\Persistence\Relation\SQL\Operation\Equal(new \Alvine\Persistence\Relation\Field\Varchar('myTable', 'fieldA'), 1),
    new \Alvine\Persistence\Relation\SQL\Operation\Equal(new \Alvine\Persistence\Relation\Field\Varchar('myTable', 'fieldB'), null),
    new \Alvine\Persistence\Relation\SQL\Operation\Equal(new \Alvine\Persistence\Relation\Field\Varchar('myTable', 'fieldC'), 'Mein Wert'),
    new \Alvine\Persistence\Relation\SQL\Operation\Equal(new \Alvine\Persistence\Relation\Field\Varchar('myTable', 'fieldD'), 0));

echo (string) $operation;
// -> (`myTable`.`fieldA`=1) OR (`myTable`.`fieldB`=NULL) OR (`myTable`.`fieldC`='Mein Wert') OR (`myTable`.`fieldD`=0)

Funktionen

Identisch zu den Operatoren funktionieren die Funktionen. Die einzelnen funktionen erwarten je nach Funktion unterschiedlich viele Argumente.

$function=new \Alvine\Persistence\Relation\SQL\Functions\FindInSet(2, '1,2,3,4');

echo (string) $function;
// -> FIND_IN_SET(2,'1,2,3,4')

$function=new \Alvine\Persistence\Relation\SQL\Functions\Concat('A','B','C','D', null, true);

echo (string) $function;
// -> CONCAT('A','B','C','D',NULL,TRUE)

$function=new \Alvine\Persistence\Relation\SQL\Functions\Lower('A');

echo (string) $function;
// -> LOWER('A')

SQL Status Codes

Die Klasse StatesCodes enthält die meisten der spezifizierten SQL-Fehlercodes.

Alle Codes sind Als Konstante wie folgt angelegt.

const SUCCESSFUL_COMPLETION='00000';
const WARNING='01000';
const WARNING_CURSOR_OPERATION_CONFLICT='01001';

Alle Codes sind wie folgt in $errorCodes eingetragen.

self::SUCCESSFUL_COMPLETION=>'Successful completion',
self::WARNING=>'Warning',
self::WARNING_CURSOR_OPERATION_CONFLICT=>'Cursor operation conflict',

Cache

Für die Zwischenspeicherung von Objekten kann der CacheManager verwendet werden. Dieser ist eine Erweiterung der ObjectStorage Klasse und fügt hauptsäachlich die Methode CacheManager->retrieveOrRefresh() hinzu. Diese Methode lädt ein Objekt aus dem im CacheManager definiertem Objektspeicher und verwendet im Anschluß die Meßpunkte um die Gültigkeit des Objektes zu bestimmen.

Ist das Objekt gütltig, so wird das aus dem Speicher geladene Objekt zurückgegeben, ansonsten wird das Objekt für ungültig erklärt und die Hook-Methode CacheManager->refresh() aufgerufen.

Im folgenden Beispiel wird ein Objekt MyObject erstellt. Jedesmal wenn sich die temporäre Datei ändert, wird das Objekt für ungültig erklärt und die Methode MyObject->refresh() aufgerufen.

/**
 * Beispiel-Objekt
 */
class MyObject extends \Alvine\Cache\CacheObject {

    public function __construct(\Alvine\IO\File\File $file) {
        parent::__construct();
        $this->examiner->append(new \Alvine\Cache\Probe\FileProbe($file));

        $this->refresh=function() {
            echo "refresh wurde aufgerufen\n";
        };
    }

}

$cacheManager=\Alvine\Cache\CacheManager::getDefaultCacheManager();

/** Temporäre Datei anlegen */
$file=\Alvine\IO\File\TemporaryFile::createUndefined();
$file->dontDeleteOnExit();

/** Zu cachende Objekt anlegen */
$obj=new MyObject(1, $file);


$cacheManager->retrieveOrRefresh($obj);
// -> refresh wurde aufgerufen
$cacheManager->retrieveOrRefresh($obj);
// Keine Ausgabe 
$cacheManager->retrieveOrRefresh($obj);
// Keine Ausgabe 
$cacheManager->retrieveOrRefresh($obj);
// Keine Ausgabe 
// Probe-Datei löschen
\unlink((string) $file);

/** Dadurch ist das Objekt nicht mehr aktuell und muss neu erstellt werden */
$cacheManager->retrieveOrRefresh($obj);
// -> refresh wurde aufgerufen

Weitere Proben erlauben es den Inhalt einer Datei FileContentProbe oder den Inhalt eines Verzeichnisses DirectoryProbe rekursiv zu überwachen.

Uniform Resource Identifier

Eine Uniform Resource Identifier - kurz URI - ist die englische Abkürzung für “einheitlicher Bezeichner für Ressourcen”. Das Schema einer URI sieht folgendermassen aus

URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]

Dabei steht hier-part für eine optionale authority und den path. Ist eine Authority vorhanden, beginnt sie mit zwei Schrägstrichen, und der Pfad muss mit einem Schrägstrich beginnen. Nach dem aktuellen Standard RFC 3986 besteht ein URI aus fünf Teilen:

  foo://example.com:8042/over/there?name=ferret#nose
  \_/   \______________/\_________/ \_________/ \__/
   |           |            |            |        |
scheme     authority       path        query   fragment
   |   _____________________|__
  / \ /                        \
  urn:example:animal:ferret:nose

Nur das Schema und der Pfad müssen in jedem URI vorhanden sein.

  • scheme (Schema)
  • authority (Anbieter)
  • path (Pfad)
  • query (Abfrage)
  • fragment (Teil)

Einige Beispiele für unterschiedliche URI:

  • http://de.wikipedia.org/wiki/Uniform_Resource_Identifier
  • ftp://ftp.is.co.za/rfc/rfc1808.txt
  • file:///C:/Users/Benutzer/Desktop/Uniform%20Resource%20Identifier.html
  • file:///etc/fstab
  • geo:48.33,14.122;u=22.5
  • ldap://[2001:db8::7]/c=GB?objectClass?one
  • gopher://gopher.floodgap.com
  • mailto:[email protected]
  • sip:[email protected]
  • news:comp.infosystems.www.servers.unix
  • data:text/plain;charset=iso-8859-7,%be%fa%be
  • tel:+1-816-555-1212
  • telnet://192.0.2.16:80/
  • urn:oasis:names:specification:docbook:dtd:xml:4.1.2
  • git://github.com/rails/rails.git
  • crid://broadcaster.com/movies/BestActionMovieEver

Neue URI erstellen

Über die Klasse \Alvine\Net\Resource\URI wird ein neues URI Objekt erstellt. Beispiel für eine MYSQL URI

$uri = new \Alvine\Net\Resource\URI('mysql://{NAME}:{PASSWORT}@192.168.1.00/{DATENBANK}/{TABELLE}');
echo (string)$uri;
// Ergebnis -> mysql://{NAME}:{PASSWORT}@192.168.1.00/{DATENBANK}/{TABELLE}

Diese Klasse bildet eine URI ab und ist im wesentlichen für Stringoperationen gedacht.

Bei der Übergabe einer URI im Konstruktor, wird diese in Ihre Einzelteile zerlegt

  • scheme
  • local
  • user
  • password
  • port
  • host
  • path
  • query
  • fragment

Mit den Funktionen getScheme, getPassword, usw. bekommt man die Teile zurück.

URI Query hinzufügen

Mit der Methode “setQueryValue” können weitere Parameter an die URL angehängt werden.

$uri = new \Alvine\Net\Resource\URI('http://www.example.com/main.html');
$uri->setQueryValue('name', 'max mustermann');
echo $uri;
// Ergebnis -> http://www.example.com/main.html?name=max%20mustermann

URI in einer HTML Seite ausgeben

Wenn man die URI in einer HTML Seite ausgeben möchte, muss man vorher das Trennzeichen ändern

$uri = new \Alvine\Net\Resource\URI('http://www.example.com/');
$uri->setQueryValue('hans','ma ns');
$uri->setQueryValue('name','frant');
$uri->setQuerySeparator('&amp;');
echo $uri;
// Ergebnis -> http://www.example.com/?hans=ma%20ns&amp;name=frant

Body

Die Body Klasse bildet den Inhalt des Requests (Anfrage) oder der Response (Antwort) ab.

Neues Body Objekt

Ein Body Objekt kann mit einem String oder einem Content Objekt erzeugt werden.

// \Alvine\Types\Mime\Content Objekt
$content = new \Alvine\Types\Mime\Text('Mein Inhalt');
$body = new \Alvine\Net\Http\Body($content);
  
// String und MimeType
$content = 'Mein Inhalt';
$body = new \Alvine\Net\Http\Body($content, new \Alvine\Types\Mime\MIMEType(\Alvine\Types\Mime\MIMEType::TEXT, \Alvine\Types\Mime\Text::HTML));

Werte auslesen

Auf den Inhalt eines Bodys kann mit der Mehtode getContent() zugegriffen werden. Das Objekt hat zusätzlich auch die Methode __toSrting() implementiert, so wird der Inhalt direkt zurück gegeben.

$body = new \Alvine\Net\Http\Body(new \Alvine\Types\Mime\Text('Mein Inhalt'));
// __toString()
echo $body; // Mein Inhalt
 
// Rückgabe ist ein \Alvine\Types\Mime\Content Objekt
$content = $body->getContent();
// Ergebnis -> Mein Inhalt

Json Werte auslesen

Wenn der Content vom Type “application/json” ist, kann über die Methode getValueMap() direkt eine Map zurück geliefert werden. Das Json steht dann als \Alvine\Types\Map zu Verfügung.

$content = '{
            "Herausgeber": "schukai",
            "Nummer": "1234-5678-9012-3456",
            "Deckung": 2e+6,
            "Waehrung": "EURO",
            "Inhaber": {
                "Name": "Mustermann",
                "Vorname": "Max",
                "maennlich": true,
                "Hobbys": [ "Reiten", "Golfen", "Lesen" ],
                "Alter": 42,
                "Kinder": [],
                "Partner": null
            }
            }';
$body = new \Alvine\Net\Http\Body($content, new \Alvine\Types\Mime\MIMEType(\Alvine\Types\Mime\MIMEType::APPLICATION_KEY, 'json'));
$map = $body->getValueMap();
  
echo $map->getValue('Herausgeber'); 
// Ergebnis -> schukai

Session

Der Datenaustausch im Web erfolgt über das zustandslose Protokolle HTTP. Da das Protokoll vom Design keinen Zustand abbildet, kann der Protokollstack auf komplizierte Strukturen verzichten. Dies ist sicherlich ein Baustein für den großen Erfolg von HTTP. Leider hat dieser Verzicht auf einen Status für Softwareentwickler gravierende Nachteile. Der Entwickler einer Webanwendung weiß bei einer Anfrage nicht von welchem Benutzer die Anfrage kommt und welche Anfragen vorausgegangen sind. Der Entwickler muß sich also um die Implementierung selber kümmern.

Die Summe aller Anfragen, die ein Besucher an eine Webanwendung richtet wird als eindeutige Sitzung (englisch Session) bezeichnet. In PHP gibt es bereits eine fertige Implementierung der Session, die den Entwickler alle Aufgaben abnimmt. Um die Session in Alvine integrieren zu können und alle Vorteile die Alvine bietet, dem Entwickler zugänglich zu machen, gibt es die Session-Klasse.

Die Session-Klasse kapselt die notwendigen Informationen und Methoden. Um eine Session zu erstellen muss nur ein neues Session-Objekt erstellt werden. Zum speichern der Session kann das Session-Objekt an eine Storage-Instanz übergeben werden.

Session erstellen und speichern

In dem folgenden Beispiel wird das Session-Objekt in das Verzeichnis \tmp\session gespeichert.

// Session erstellen
$session = new \Alvine\Net\Session\Session();
  
// Storage-Objekt
$source = new \Alvine\Persistence\Provider\File\DataSource('/tmp/session');
$object = new \Alvine\Persistence\Provider\File\DataObject($source);
$storage = new \Alvine\Persistence\ObjectStorage($object);
     
// Die Session speichern
$storage->writeObject($session);

Session laden

Eine gespeicherte Session kann bei einem weiteren Request über die Session-ID wieder geladen werden. In diesem Beispiel wird die Session-ID über einen Request übergeben.

// Storage-Objekt
$source = new \Alvine\Persistence\Provider\File\DataSource('/tmp/session');
$object = new \Alvine\Persistence\Provider\File\DataObject($source);
$storage = new \Alvine\Persistence\ObjectStorage($object);

// Session laden
$session = $storage->getObjectByID($sid);

Wert in der Session speichern

Werte können in einer Session einfach durch die Angabe einer Eigenschaft gespeichert werden.

// Session erstellen
$session = new \Alvine\Net\Session\Session();

// Der Session eine Session-Variable zuordnen
$session->meineSchluessel = 'Mein Wert';
  
// Wert aus einer Session auslesen
echo $session->meineSchluessel;
// Ergibt -> Mein Wert

Nonces verwalten

Ein Nonce (oder Zufallstoken) ist ein Sicherheitsfeature und wird für einen Request erstellt und kann mit einem Formular zurückgesendet werden. Jeder Nonce kann nur einmal verwendet werden. Wird ein Request mit einem ungültigen Nonce gesendet kann die Anwendung die Verarbeitung eines Formulars oder des Requests ablehnen.

// Session erstellen
$session = new \Alvine\Net\Session\Session();
$nonce = new Alvine\Net\Session\Nonce();
$token = $nonce->getToken();
$session->addNonce($nonce);
  
// Session speichern und Token in Formular einbauen
// ... hier session speichern ...
echo '<input type="hidden" value="'.$token.'">';
// Ergibt -> <input type="hidden" value="e1cbc9b62a8046c597f9219488e65036">

Beim nächsten Request kann über die Methode Session::redeemedNonce() der Token überprüft werden.

$session = new \Alvine\Net\Session\Session();
if($session->redeemedNonce($nonce)) {
    echo 'OK';
} else {
    echo 'Der Request war fehlerhaft.';
}

E-Mail

Die E-Mail1 ist zum einen ein System zur computerbasierten Verwaltung von briefähnlichen Nachrichten und deren Übertragung über Computernetzwerke, insbesondere über das Internet. Zum anderen werden auch die auf diesem elektronischen Weg übertragenen Nachrichten selbst als E-Mails bezeichnet.

Mail-Klasse

Mit Hilfe der Mailklasse \Alvine\Net\Mail\Mail lässt sich eine E-Mail zusammenstellen und per SMTP-Client versenden. Alternativ kann die Hilfsmethode \Alvine\Net\Mail\Mail::send() direkt verwendet werden. Diese erstellt einen SMTP-Client und versendet sich selber mit diesem Client.

// Neue Mail erstellen
$mail=new \Alvine\Net\Mail\Mail();

$to=$to??'[email protected]';
$from=$from??'[email protected]';
$mailserver=$mailserver??'smtp.example.com';
$client=$client??'me.example.com'; // Name des clients
$port=$port??25;

/**
 * Mailparameter übergeben und mit dem 
 * Standard SMTP-Client versenden
 */
try {

    $mail->addTo($to)
        ->setFrom($from)
        ->setSubject('Testmail')
        ->addAlternativeParts('<html>', 'plain')
        ->send($mailserver, $port, $client);
} catch(\Alvine\Net\Mail\SMTPClientException $e) {
    echo "Exception: ".$e->getMessage();
}

Will man ein Log der Serverkommunikation, so kann man diese mit einem Logger mitschneiden.

// Neue Mail erstellen
$mail=new \Alvine\Net\Mail\Mail();

$to=$to??'[email protected]';
$from=$from??'[email protected]';
$mailserver=$mailserver??'smtp.example.com';
$client=$client??'me.example.com';
$port=$port??25;
// TESTWERTE NICHT FÜR DOKU

/**
 * Mailparameter übergeben und mit dem 
 * Standard SMTP-Client versenden
 */
try {

    // Handler
    $handler=new \Alvine\Util\Logging\Handler\Memory();

    // Logger
    $logger=\Alvine\Util\Logging\Logger::getInstance(\Alvine\Util\Logging\LoggerDefaultName::MESSAGE);
    $logger->addHandler($handler);
    $handler->setThreshold(\Alvine\Util\Logging\Level::ALL);

    $mail->addTo($to)
        ->setFrom($from)
        ->setSubject('Testmail')
        ->addAlternativeParts('<html>', 'plain')
        ->send($mailserver, $port, $client);

    // Ausgabe der Logdaten
    echo $handler->getBuffer();
    // Ergebnis -> Dies ist ein Log-Trace und eine Log-Debug Zeile!
} catch(\Alvine\Net\Mail\SMTPClientException $e) {
    echo "Exception: ".$e->getMessage();
}

Das Ergebnis aus dem Logger ist dann in etwa so:

220 mailhog.example ESMTP MailHog
EHLO mailhog
250-Hello mailhog
250-PIPELINING
250 AUTH PLAIN
MAIL FROM:<[email protected]>
250 Sender [email protected] ok
RCPT TO:<[email protected]>
250 Recipient [email protected] ok
DATA
354 End data with <CR><LF>.<CR><LF>
Date: 11 Sep 2019 18:00:37 +0000
From: [email protected]
Reply-To: [email protected]
Subject: Testmail
To: [email protected]
Message-ID: <ca24d237-518d-4cd7-acd1-007ca4d72ada@1f4cd243>
MIME-Version: 1.0
Content-Type: MULTIPART/alternative; BOUNDARY="8323329-704665374-1568224837=:1889"

--8323329-704665374-1568224837=:1889
Content-Type: TEXT/plain; CHARSET=UTF-8
Content-Transfer-Encoding: QUOTED-PRINTABLE

plain
--8323329-704665374-1568224837=:1889
Content-Type: TEXT/html; CHARSET=UTF-8
Content-Transfer-Encoding: QUOTED-PRINTABLE

<html>
--8323329-704665374-1568224837=:1889--

.
250 Ok: queued as psjB0TUNhB61XKXQtlVmh4oBCiRZX9CZ-II9oTIBjCY=@example
QUIT
221 Bye

SMTP

SMTP2 ist ein Protokoll der Internetprotokollfamilie, das zum Austausch von E-Mails1 in Computernetzen dient. Es wird dabei vorrangig zum Einspeisen und zum Weiterleiten von E-Mails verwendet. Zum Abholen von Nachrichten kommen andere, spezialisierte Protokolle wie POP3 oder IMAP zum Einsatz.

SMTP-Server nehmen traditionell Verbindungen auf Port 25/TCP (Standard-MTA) entgegen. Auf Port 465/TCP nur mit SSL/TLS und auf Port 587/TCP nur als MSA/für Mail-Clients, häufig mit STARTTLS.

SMTP-Client

Mit Hilfe des SMTP-Clients lassen sich E-Mails über das SMTP Protokoll versenden.

// Neue Mail erstellen
$mail=new \Alvine\Net\Mail\Mail();
/**
 * mail initialisieren
 */
$mailserver=$mailserver??'smtp.example.com';
$client=$client??'me.example.com'; // Name des clients
$port=$port??25;



$smtp=new \Alvine\Net\Mail\SMTPClient($mailserver, $port);
/** Absendender Host */
$smtp->setHostname($client);

try {
    $smtp->enableLogging()
        ->sendMail($mail);
} catch(\Exception $e) {
    // Fehlerbehandlung
}

echo $smtp->getLog();

Beim Versand einer E-Mail, muss in der EHLO/HELO Begrüßung ein gültiger Hostname stehen. Aus der Spezifikation RFC5321:

In the EHLO command, the host sending the command identifies itself; the command may be interpreted as saying “Hello, I am ” (and, in the case of EHLO, “and I support service extension requests”).

The domain name given in the EHLO command MUST be either a primary host name (a domain name that resolves to an address RR) or, if the host has no name, an address literal, as described in Section 4.1.3 and discussed further in the EHLO discussion of Section 4.1.4.*

Im Standard wird die PHP-Methode gethostname() verwendet. In Containern oder lokalen Installationen wird hier oft ein nicht im DNS auflösbarer Name wie localhost zurückgegeben. In diesen Fällen muss man mit der Methode \Alvine\Net\Mail\SMTPClient::setHostname(string $name) einen gültiger Domainname setzen.

1

kurz Mail; engl. electronic mail für „elektronische Post“ oder E-Post 2: Das Simple Mail Transfer Protocol

Title: Fehlersuche und -behandlung in PHP Skripten

PHP-Fehlerbehandlung

Exception gegen Error-Code

PHP stellt mit der Fehlerbehandlung über Errors ein Möglichkeit dar, auf Fehler zu reagieren. Im Framework wird allerdings in den meisten Fällen, besonders wenn es um die Arbeit mit Ressourcen geht Exception geworfen, anstatt einen Error-Code zurückzugeben.

Die Entscheidung muß je nach Klasse und Methode neu getroffen werden und gut abgewogen werden. Der große Vorteil der Verwendung von Exception im Framework ist wie auch z.B. Herb Sutter meint:

Exceptions können Fehler innerhalb des Codes an jeder Stelle (auch in einem Konstruktor) melden. Im Konstruktor können z.B. keine Error-Code zurückgegeben werden.

Exceptions können nicht ignoriert werden. Ein Error-Code muss explizit abgefangen und geprüft werden, eine Exception macht sich immer bemerkbar.

Die Klasse ErrorHandler

Leider ist das Handling von PHP-Fehlern nicht trivial. Aus diesem Grund und zur Vereinheitlichung der Fehlerbehandlung bietet Alvine mit der Klasse \Alvine\Core\ErrorHandler eine Möglichkeit zur Verfügung, Errors in Exceptions umzuwandeln. Alvine hängt sich dafür in die PHP-Fehlerbearbeitung ein und wirft im Falle eines Auftretenden PHP-Fehlers eine \Alvine\Core\ErrorException. Die weitere Behandlung muss dann vom Skript übernommen werden.

/**
 * Error-Handler registrieren
 */
$errorHandler = \Alvine\Util\Error\PHPHandler::getInstance();
$errorHandler->register();
  
/**
 * Systemfehler auslösen.
 */
trigger_error('FEHLER!');

Will man im Skript auf einen Fehler reagieren, so kann man das über einen try-Block abfangen. Allerdings funktioniert das nicht bei PHP-Fehlern. Das folgende Beispiel wird mit einem PHP-Fehler beendet.

try {
  trigger_error('FEHLER!');
} catch(Exception $e) {
  // Behandlung der Exception
}

Hier kann die Klasse \Alvine\Core\ErrorHandler eingesetzt werden und den Aufruf der PHP Funktion kapseln

try {
    Alvine\Util\Error\PHPHandler::execute('\Alvine\Core\FrameworkException', function() {
       @trigger_error('FEHLER!');
    });
} catch(Exception $e) {
    echo 'Exception: '.$e->getMessage();
    // Ergebnis -> Exception: E_USER_NOTICE. Error message was "FEHLER!" in file example.php
}

Zeitmessung

Performance ist sehr wichtig und an vielen Stellen muss der Entwickler die Dauer von Operationen prüfen.

xdebug & WinCacheGrind

Um das Ausführungsverhalten des gesamten Quelltext zu beurteilen und die zeitliche Ausführung zu messen, ist der Einsatz einer Bibliothek wie xdebug sinnvoll. Optimal geht die Auswertung der Daten mit WinCacheGrind. Hier können die genauen Ausführungszeiten der einzelnen Schritte gemessen und analysiert werden.

Messung mittels Profiler-Klasse

Oftmals stehen diese Bibliotheken in der Umgebung nicht zur Verfügung, oder eine Anwendung ist für eine Einzelmessung zu aufwendig. Hier kann die Alvine-Klasse Alvine\Util\Profiling\Profiler helfen. Diese Klasse nimmt einen Meßwert und kann über die Methode getDuration() die Differenz ausgeben.

$profiler = \Alvine\Util\Profiling\Profiler::getInstance('test');
$profiler->record('start');
echo "\n";
echo 'Anfang: '.$profiler->getDuration().' s';
echo "\n";
 
// Mache irgendetwas ... 
$profiler->record('ende');
echo "\n";
echo 'Ende: '.$profiler->getDuration().' s';
echo "\n";

// Ergebnis -> Anfang: 0.00016212463378906 s 
//            Ende: 0.00041818618774414 s

Einen einfacheren Ansatz bietet die statische Methode Profiler::execute(). Der Methode kann ein Zähler n und ein Callback fkt übergeben werden. Die Methode führt den Callback fkt, dann n mal aus und misst die Ausführungszeit.

$profiler = \Alvine\Util\Profiling\Profiler::getInstance('test');
$profiler->record('start');
echo "\n";
echo 'Anfang: '.$profiler->getDuration().' s';
echo "\n";
 
// Mache irgendetwas ... 
$profiler->record('ende');
echo "\n";
echo 'Ende: '.$profiler->getDuration().' s';
echo "\n";

// Ergebnis -> Anfang: 0.00016212463378906 s 
//            Ende: 0.00041818618774414 s

Logbuch und Fehlersuche

Für die Fehlersuche steht mit der Logger-Klasse ein Werkzeug für die Systemanalyse bereit. Die Logger-Klasse ist dabei so konzipiert, das Sie nicht zu schwerfällig ist (siehe Log4j), aber trotzdem eine gute und erweiterbare Funktionalität bereitstellt.

Loglevel

Der Loglevel in der Konfiguration gibt an welche Meldungen von dem Handler verarbeitet werden. Der Loglevel ist ein Filter der in der Reihenfolge ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF ausgewertet wird.

TypBeschreibung
ALLEs werden alle Log-Nachrichten die vom Logger an den Handler gesendet werden vom Handler verarbeitet.
TRACEEs werden alle Meldungen die mit Logger::logTrace(), Logger::logDebug(), Logger::logInfo(), Logger::logWarn(), Logger::logError() und Logger::logFatal() geschrieben wurden vom Handlerverarbeitet.
DEBUGEs werden alle Meldungen die mit Logger::logDebug(), Logger::logInfo(), Logger::logWarn(), Logger::logError() und Logger::logFatal() geschrieben wurden vom Handler verarbeitet.
INFOEs werden alle Meldungen die mit Logger::logInfo(), Logger::logWarn(), Logger::logError() und Logger::logFatal() geschrieben wurden vom Handler verarbeitet.
WARNEs werden alle Meldungen die mit Logger::logWarn(), Logger::logError() und Logger::logFatal() geschrieben wurden vom Handler verarbeitet.
ERROREs werden alle Meldungen die mit Logger::logError() und Logger::logFatal() geschrieben wurden vom Handler verarbeitet.
FATALEs werden nur Meldungen die mit Logger::logFatal() geschrieben wurden vom Handler verarbeitet.
OFFEs werden keine Logeinträge verarbeitet.

Logfunktionen und Auswirkung

Mit Hilfe der folgenden Logfunktionen können die Logeinträge im jeweiligen Level geschrieben werden.

MethodeEinsatzgebietZustand der AnwendungAktion
logTrace()Loggen einzelner Schritte im Programmablauf.FehlersucheTiefgreifendes Debugging des Entwicklers
logDebug()Loggen von Daten an wichtigen Stellen im Code, die zum Debuggen von Fehlern hergenommen werden könnenFehlersucheDebugging des Entwicklers oder Administrators
logInfo()Informationen für den Administrator zum Verhalten der AnwendungNormalKeine
logWarn()Warnungen über mögliche Fehler in der AnwendungDie Funktionsweise ist noch nicht eingeschränkt, aber es kann zu nicht gewollten Ergebnissen kommen.Überprüfen der Warnung und nach Möglichkeit Feinjustierung der Konfiguration
logError()Fehler in der AnwendungDie Funktionsweise der Anwendung ist stark eingeschränkt.Identifizierung des Fehlers und Beseitigung der Fehlerquelle veranlassen
logFatal()Fehler die ein kritisches Verhalten der Anwendung herbeiführenDie Anwendung oder wichtige Funktionen funktionieren nichtSofortige Maßnahmen einleiten

Standard Logger

Es gibt verschiedene Standard-Logger, die für bestimmte Aufgaben bestimmt sind.

NameBeschreibungBeispiel
applicationLogeinträge der Anwendung (Verwendung direkt in der Anwednungsklasse)
databaseLogeinträge im Zusammenhang mit DatenbankenFehler bei MySQL-Select
defaultStandardlogger (keine Verwendung im Framework)
frameworkMeldungen des Alvine-Frameworks, sollten nicht von Anwendungen und Komponenten verwendet werden
httpSpezielle Netzwerklogging für das HTTP-ProtokollDie HttpClient Klasse loggt hier jeden Request im TRACE Level
ioInput/Output Logging: Alle DateifunktionenFehler bei in den IO-Klassen
networkingLogeinträge die das Networking betreffenÜbertragene Daten des Sockets
presenterLogeinträge die in einer Presenter-Klasse geschrieben werden
routingLogeinträge im Zusammenhang mit dem RoutingLogging der Router-Klasse

Logbuch

Das Logging kann über eine Konfigurationsdatei gesteuert werden. Je nach gewünschtem Ausgabeumfang kann der entsprechende Filter gesetzt werden. Die Reihenfolge der Filterung stellt sich dabei wie folgt dar: ALL > TRACE > DEBUG > INFO > WARN > ERROR > FATAL > OFF

Beispielcode für das Erstellen eines Loggers.

// Neue Handler für die Ausgabe in eine Datei erstellen.
$handler = new \Alvine\Util\Logging\Handler\File('alvine.log');
// Neuer Logger
$logger = \Alvine\Util\Logging\Logger::getLogger();
// Als Ausgabe soll eine einfache tab-getrennte Liste
// erstellt werden
$formatter = new \Alvine\Util\Logging\Formatter\Plain();
// Zuweisung des handlers und des Formatters
$logger->addHandler($handler);
$handler->setFormatter($formatter);
// Über den Schwellwert kann gesteuert werden,
// welche Meldungen ausgegeben werden sollen.
// In diesem Fall sollen nur Infomeldungen, Warnings,
// Errors und Fatals protokolliert werden.
$handler->setThreshold(\Alvine\Util\Logging\Level::INFO);
  
// Meldungen mit unterschiedlichen Level
// ausgeben.
$logger->logTrace('Log-Trace');
$logger->logDebug('Log-Debug');
$logger->logInfo('Log-Info');
$logger->logWarn('Log-Warn');
$logger->logError('Log-Error');
$logger->logFatal('Log-Fatal');

// Ergebnis -> 2017-01-02 09:58:22	INFO	Log-Info
//             2017-01-02 09:58:22	WARN	Log-Warn
//             2017-01-02 09:58:22	ERROR	Log-Error
//             2017-01-02 09:58:22	FATAL	Log-Fatal

Handler

Memory

Will man die Logdaten direkt ausgeben (zum Beispiel bei der Entwicklung) kann der MemoryHandler zum Einsatz kommen. Dieser erlaubt es die Lognachrichten als Zeichenkette auszugeben.

// Handler
$handler = new \Alvine\Util\Logging\Handler\Memory();
  
// Logger
$logger = \Alvine\Util\Logging\Logger::getInstance();
$logger->addHandler($handler);
$handler->setThreshold(\Alvine\Util\Logging\Level::ALL);
  
/** do something */
$logger->logTrace('Dies ist ein Log-Trace');
$logger->logDebug(' und eine Log-Debug Zeile!');
  
  
// Ausgabe der Logdaten
echo $handler->getBuffer();
// Ergebnis -> Dies ist ein Log-Trace und eine Log-Debug Zeile!

Chrome Logger

Der ChromeHandler arbeitet mit der Chrome-Erweiterung Chrome Logger zusammen. Die Chrome-Erweiterung kann über den Chrome-Store oder diesen Link installiert werden. Für einen ersten Test kann man folgenden Beispielcode verwenden.

// ChromeHandler
$handler = new \Alvine\Util\Logging\Handler\Chrome();
// Logger
$logger = \Alvine\Util\Logging\Logger::getLogger();
$logger->addHandler($handler);
  
// Meldungen mit unterschiedlichen Level
// ausgeben.
$logger->logTrace('Dies ist ein Log-Trace');
$logger->logDebug('Und eine Log-Debug Zeile!');

ElasticSearch

Kibana und Elasticsearch sind ein tolles Gespann für die Auswertung von Lagdateien. Alvine bietet die Möglichkeit direkt nach ElasticSearch zu loggen. dazu muss nur ein Handler

// Handler
$uri = new \Alvine\Net\Resource\URI('http://www.example.com/');
$handler = new \Alvine\Util\Logging\Handler\ElasticSearch($uri, '/alvine-logs/entry/');
// Logger
$logger = \Alvine\Util\Logging\Logger::getLogger();
$logger->addHandler($handler);
   
// Meldungen mit unterschiedlichen Level
// ausgeben.
$logger->logTrace('Dies ist ein Log-Trace');
$logger->logDebug('Und eine Log-Debug Zeile!');

Loggr.com

Der Dienst loggr.net bietet eine zentrale Stelle, für die Verarbeitung von Lognachrichten. Das Alvine Framework bietet mit der LoggrHandler-Klasse die einfache Möglichkeit Lognachrichten direkt zu loggr zu senden. Dazu muss lediglich ein Handler mit dem entsprechenden API-Schlüssel und dem LogSchlüssel übergeben werden.

// LoggrHandler

$apiKey='GEHEIM';
$logKey='SECRET';

$handler = new \Alvine\Util\Logging\Handler\Loggr($apiKey, $logKey);
// Logger
$logger = \Alvine\Util\Logging\Logger::getLogger();
$logger->addHandler($handler);
   
// Meldungen mit unterschiedlichen Level
// ausgeben.
$logger->logTrace('Dies ist ein Log-Trace');
$logger->logDebug('Und eine Log-Debug Zeile!');

Übersicht

Alvine bietet mit den Klassen aus dem Types Namensraum einige interessante Konzepte für den Umgang mit unterschiedlichen Variablen-Typen an.

Inhalt

Zeichenketten

StringType

Die Zeichenketten-Klasse \Alvine\Types\StringType bringt die benötigte Unterstüzung für UTF-8 mit und stellt nützliche Methoden zur Bearbeitung von UTF-8 Zeichenketten zur Verfügung.

Note

Die Klasse hieß in einer früheren Version \Alvine\Types\String. Mit der Einführung des Schlüsselwortes string in PHP wurde die Klasse umbenannt.

Die Klasse ist auf die Verwendung der Multi-Byte Erweiterung mb_string zugeschnitten.

$string = new \Alvine\Core\StringType();
$string->string='Ich esse gerne Äpfel';  // Inhalt setzen
echo $string;  // Gibt folgendes aus: Ich esse gerne Äpfel

Wie in dem obigen Beispiel zu sehen, kann der Zugriff auf den gespeicherten Wert auch über die Eigenschaft string erfolgen.

Da für ein UTF-8 Zeichen eine unterschiedliche Anzahl von Bytes benutzt wird, ist die Länge einer Zeichenkette ungleich der Anzahl der Bytes die eine Zeichenkette belegt.

Mit der Methode \Alvine\Types\StringType::bytes() kann die Anzahl der Bytes ermittelt werden und mit der Methode \Alvine\Types\StringType::length() wird die Anzahl der Zeichen ermittelt.

Die Zeichenkette Iñtërnâtiônàlizætiøn hat 20 Zeichen, belegt allerdings 27 Bytes im Speicher.

$string=new \Alvine\Types\StringType('Iñtërnâtiônàlizætiøn');

echo $string->length();
// → 20

echo $string->bytes();
// → 27

Über die Methode \Alvine\Types\StringType::setEncoding() kann das Ausgabeformat für die Zeichenkette gesetzt werden.

$string=new \Alvine\Types\StringType('Iñtërnâtiônàlizætiøn');

echo (string) $string;
// → Iñtërnâtiônàlizætiøn

$string->setEncoding(\Alvine\I18n\Encoding::HTML_ENTITIES);

echo (string) $string;
// → I&ntilde;t&euml;rn&acirc;ti&ocirc;n&agrave;liz&aelig;ti&oslash;n

Die Methode \Alvine\Types\StringType::toLowerCase() bzw. \Alvine\Types\StringType::toUpperCase() wandeln die Groß- und Kleinschreibung um.

Mit der Methode \Alvine\Types\StringType::trim() können Leerzeichen am Anfang und Ende der Zeichenkette entfernt werden. Wird ein Zeichen übergeben \Alvine\Types\StringType::trim('a'), so wird dieses, anstatt von Leerzeichen entfernt. Mit der Methode \Alvine\Types\StringType::isEmpty() wird geprüft ob eine Zeichekette leer ist.

Mit Hilfe der Methode \Alvine\Types\StringType::getLines() kann ein mehrzeilige Zeichenkette in einzelne Zeilen getrennt werden. Der Rückgabewert dieser Methode ist ein Objekt vom Typ \Alvine\Types\StringList

$string=new \Alvine\Types\StringType('
1. Zeile
2. Zeile
3. Zeile
    ');


$lines=$string->getLines();
foreach($lines AS $line) {
    echo '|'.$line."|\n";
}

Das Ergbnis ist in diesem Fall folgende Ausgabe.

||
|1. Zeile|
|2. Zeile|
|3. Zeile|
|    |

Die erste Zeile der Ausgabe zeigt, dass die Eingabe mit einem Zeilenumbruch beginnt.

Soll statt ein Zeilenumbruch ein anderes Trennzeichen Anwendung finden, so kann die Methode \Alvine\Types\StringType::explode($delimiter) verwendet werden.

Die Hilfsfunktion \Alvine\Types\StringType::getHtmlEntities() kann die Zeichenkette in HTML Entities codiert werden. Der Rückgabewert ist die umgewandelte Zeichenkette.

Mit der Methode \Alvine\Types\StringType::append($string) lässt sich eine Zeichenkette anhängen. Der Parameter $string kann entweder eine PHP-Zeichenkette oder ein Objekt vom Typ \Alvine\Types\StringType sein.

Durch \Alvine\Types\StringType::indexOf($string, $offset) lässt sich das erste Auftreten des Zeichen oder der Zeichenkette string ermitteln. Das Ergebnis ist identisch zu mb_strpos. Für die Suche des letzten Auftretens kann die Methode \Alvine\Types\StringType::lastIndexOf($string, $offset) zum Einsatz kommen.

\Alvine\Types\StringType::getSubstring($start, $length) liefert eine Teilzeichenkette zurück.

Mit Hilfe von \Alvine\Types\StringType::replace($pattern, $replacement) kann eine Teilzeichenkette ersetzt werden.

\Alvine\Types\StringType::contains($string) und \Alvine\Types\StringType::match($pattern) suchen nach dem Parameter und geben true bzw. false zurück. Match verwendet für den Vergleich mb_ereg und contains verwendet indexOf.

Um Zeichenketten vergleichen zu können, steht die Methode \Alvine\Types\StringType::compareTo($string) zur Verfügung.

Die statische Methode \Alvine\Types\StringType::encode($string, $encoding) erlaubt die Konvertierung einer Zeichenkette in ein anderes Format.

StringList

Die Klasse \Alvine\Types\StringList stellt eine Sammlung von Zeichenketten des Type \Alvine\Types\StringType bereit. Die Klasse implementiert die Standard-Interfaces \Iterator, \ArrayAccess und \Countable.

Character

Die Klasse \Alvine\Types\Character stellt Methoden und Konstanten zur Behandlung von Zeichen zur Verfügung.

Die Methode \Alvine\Types\Character::isUpperCase() prüft ob ein Zeichen ein Großbuchstabe ist.

  • A ergibt true
  • a ergibt false
  • ? ergibt false, da es kein Großbuchstabe ist

Die Methode \Alvine\Types\Character::isLowerCase() prüft ob ein Zeichen ein Kleinbuchstabe ist.

  • A ergibt false
  • a ergibt true
  • ? ergibt false, da es kein Kleinbuchstabe ist

Die Methode \Alvine\Types\Character::isWhitespace() prüft ob ein Zeichen ein Leerzeichen ist.

Die Methode \Alvine\Types\Character::fromChar() gibt den Code eines UTF-8 Zeichens zurück.

Die Methode \Alvine\Types\Character::fromInteger() ist das Gegenstück und wandelt einen Code in das entsprechende UTF-8 Zeichens um.

Die Methode \Alvine\Types\Character::toLower() bzw. \Alvine\Types\Character::toIpper() wandelt ein Zeichen in die entsprechende Schreibweise.

Die Methode \Alvine\Types\Character::getLineFeeds() gibt eine Array mit allen in UTF-8 definierten Zeilenumbrüchen zurück.

ASCII

Die Klasse \Alvine\Types\ASCII stellt für bestimme ASCII Sonderzeichen Konstante zur Verfügung.

Für das LineFeed-Zeichen steht zum Beispiel die Konstante \Alvine\Types\ASCII::LF zur Verfügung. Der Wert der Konstante ist in diesem Fall 10.

ZeichenASCIIBeschreibung
NULL0
SOH1Dokumentenkopf
STX2Textanfang
ETX3Textende
EOT4
ENQ5
ACK6Bestätigung
BEL7Klingel
BS8Backspace
TAB9
LF10Line Feed
VT11
FF12
CR13Wagenrücklauf
SO14
SI15
DLE16
DC117
DC218
DC319
DC320
NAK21
SYN22
ETB23
CAN24
EM25
SUB26
ESC27Escape
FS28
GS29Gruppentrenner
RS30
US31Einheitstrenner
SP32Leerzeichen
DEL127
CSI155Control Sequenz Introducer

HierarchicalString

Viele Daten liegen als hierarchische Informationen vor. So z.B. die Nummerierung von Kapiteln, oder von Aufzählungslisten.

Implementierung

Die Klasse \Alvine\Types\HierarchicalString stellt eine bequeme Möglichkeit zum Arbeiten mit hierarchischen Zeichenketten dar. Als Beispiel haben wir eine Zeichenkette a.b.c.d die jeweils reduziert und erweitert werden soll.

/**
 * Neues Objekt erstellen
 */
$string = new \Alvine\Types\HierarchicalString('a.b.c.d');
 
/**
 * Durch alle Teile laufen und diese Ausgeben
 */
foreach($string AS $sub) {
    echo $sub."::";
}
 
// Ausgabe: a:🅱️:c::d::
 
/**
 * Über die Methoden addChild und removeChild läßt sich
 * die Struktur einfach bearbeiten.
 */
echo ((string)$string)."\n";                     // Zeichenkette ausgeben  a.b.c.d
echo ((string)$string->parent())."\n";           // Eine Ebene nach oben   a.b.c
echo ((string)$string->addChild('1.2.3'))."\n";  // 3 Ebenen ergänzen      a.b.c.1.2.3  
echo ((string)$string->addChild('4.'))."\n";     // Eine weitere Ebene     a.b.c.1.2.3.4      (Wichtig: der letzte Punkt wird nicht mit ausgegeben)
echo ((string)$string->parent(3))." \n";         // Drei Ebenen nach oben  a.b.c.1
 
// Anderes Trennzeichen setzen
$string->setSeparator('::');
echo (string)$string;                 // Ausgabe a:🅱️:c::1

Vergleichen von zwei Zeichenketten

Die Methode HierarchicalString::match() erlaubt es zwei Zeichenketten zu vergleichen. Dies ist insbesondere mit den zur Verfügungstehenden Wildcards ein mächtiges Werkzeug zur Auswertung von Eigenschaften.

/**
 * Diese Methode erlaubt es, einen hierarchischen String mit 
 * einem anderen String zu vergleichen und auch ein Wildcard *
 * einzusetzen. Werden zwei Sterne als letzte Zeichen angegeben **
 * so wird auch TRUE zurückgegeben, wenn der Ursprungsstring
 * länger als der Vergleichsstring ist.
 **/

$stringA = new HierarchicalString('a.b.c.d');
$stringB = new HierarchicalString('a.b.c.d');
$flag = !$stringA->match($stringB));   // TRUE, da der Filterwert $stringB mit $stringA übereinstimmt.
 
$stringB = new HierarchicalString('a.b.c.d.g');
$flag = !$stringA->match($stringB));   // FALSE, da der Filterstring länger ist.
 
// Einsatz von Wildcards
$stringA = new HierarchicalString('a.b.c.d');
$stringB = new HierarchicalString('*.b.*.d');
$flag = $stringA->match($stringB);   // TRUE, da die Wildcards für jeden Wert stehen

$stringB = new HierarchicalString('*.b.*.d.*');
$flag = $stringA->match($stringB);   // FALSE, da der Wildcards zu lang ist.
 
$stringB = new HierarchicalString('*.b');
$flag = $stringA->match($stringB);   // FALSE, nicht gleiche Länge.
 
// Wildcard für beliebige Länge
$stringB = new HierarchicalString('a.**');
$flag = $stringA->match($stringB);   // TRUE
Ein Sonderfall tritt ein, wenn beide Strings leer sind. In diesem Fall wird per Definition TRUE zurückgegeben.
$stringA = new HierarchicalString('');
$stringB = new HierarchicalString('');
$this->assertTrue($stringA->match($stringB));   // TRUE, beide identisch leer

Tags

Die Klasse \Alvine\Types\Tag ist ein Alias für die Klasse \Alvine\Types\String im Kontext der Klasse \Alvine\Types\Tags.

Die Sammelklasse für Tags \Alvine\Types\Tags implementiert die PHP-Standardinterfaces \Iterator und \Countable.

$tags=new \Alvine\Types\Tags();
$tags->add(new \Alvine\Types\StringType('tag1'));
$tags->add(new \Alvine\Types\StringType('tag2'));
$tags->add(new \Alvine\Types\StringType('tag3'));

foreach($tags AS $tag) {
    echo (string) $tag."\n";
}

// → tag1
//   tag2
//   tag3

Zahlen

Bei der Verwendung von Zahlen in Computersystemen sind einige Besonderheiten zu beachten, die in diesem Artikel kurz dargestellt werden. Im Anhang sind Links auf ausführlichere Dokumentationen genannt.

Floats

Gleitkommazahlen haben in EDV-Systemen eine begrenze Genauigkeit und können, bei Anwendern die mit der Problematik nicht vertraut sind, zu unerwarteten Ergebnissen führen. Der folgende Code wird in den meisten Fällen 7 und nicht 8 zurückgeben. Dies hat damit zu tun, das die interne Darstellung im System nicht 8, sondern 7.9999999999999991118.... entspricht. Bei der Verwendung von Gleitkommaarithmetik für die Berechnung von Geldbeträgen, können schnell ein paar Euro fehlen.

$float = floor((0.1+0.7)*10);
echo $float; // 7

Genauigkeit

Die interne Genauigkeit hängt vom System ab und liegt in der Regel bei 14 Stellen. Die gewünschte Genauigkeit kann in PHP über die Konfigurationseinstellung precision eingestellt werden.

precision=14

In den folgenden Beispiel wird eine Gleitkommazahl mit unterschiedlicher Genauigkeit betrachtet. Man sieht recht gut, das eine Erhöhung der Genauigkeit zu Problemen führen kann. Die Gründe hierzu können in dem Wikipedia-Artikel über Gleitkommazahlen nachgelesen werden.

\ini_set('precision', 14);
$float = 24.6;
echo $float; // 24.6

\ini_set('precision', 20);
$float = 24.6;
echo $float; // 24.600000000000001421

Integer

In der folgenden Tabelle sind die Repräsentationen von Ganzzahlen in einem 32bit PHP-System dargestellt. Mann erkennt ganz gut das das erste Bit für das Vorzeichen verwendet wird. Die maximal darstellbare ganzzahlige Zahl hängt von der Systemarchitektur ab und kann über Konstante PHP_INT_MAX abgefragt werden. Die Konstante PHP_INT_SIZE liefert die Anzahl der Bytes, die zur Speicherung der Zahlen verwendet werden.

Darstellung im Zahlenraum

AusgangszahlBinäre DarstellungUmwandlung in Dezimal (bindec)Bemerkung
1073741823001111111111111111111111111111111073741823
1073741824010000000000000000000000000000001073741824Überlauf
2147483647011111111111111111111111111111112147483647entspricht PHP_INT_MAX
-1111111111111111111111111111111114294967295Größte Binärzahl
-100111111111111111111111111100111004294967196
-2147483648100000000000000000000000000000002147483648über ~PHP_INT_MAX

Dieses Wissen ist auch für die Arbeit mit großen Zahlen wichtig. Definiert man zum Beispiel auf einem 64bit System

$int = 9999999999999999999;
echo $int; // 1.0E+19

so definiert man kein Integer, sondern einen Wert vom Typ Float. Verwendet man bei der Zuweisung allerdings eine explizite Typumwandlung (cast), so wird der Wert auf einen Integer “gekürzt”.

$int = (int) 9999999999999999999;
echo $int; // -8446744073709551616 

GMP

Eine Möglichkeit mit sehr großen Zahlen zu arbeiten, bietet die Erweiterung GNU Multiple Precision (gmp). Mit gmp ist es Möglich auch sehr große Werte zu verarbeiten.

$int = gmp_init("9999999999999999999");
$intString = gmp_strval($int);
var_dump($int, $intString); // resource(4) of type (GMP integer)   string(19) "9999999999999999999"

Eigenschaften

Es gibt mehrere Wege Konfigurationen zu speichern. Eine sicherlich einfache Methode und für viele Anwender verständliche Form der Darstellung, sind die von Java bekannten Eigenschaften oder Properties. Eigenschaften sind in diesem Zusammenhang einfache Schlüssel-Wert-Paare die in einer Textdatei gespeichert werden können.

Einleitendes Beispiel

Im folgenden ist eine einfache Datei mit den Schlüsseln key1 und key2 abgebildet. Die einzelnen Zeilen sind durch Zeilentrennzeichen \n voneinander getrennt.

#Das ist ein Dateikommentar.
key1=value
key2:value
#Abschlußkommentar

Aufbau der einzelnen Elemente

Schlüssel/Werte

Schlüssel-Werte können entweder durch einen Doppelpunkt oder ein Gleichheitszeichen getrennt werden. Weder die Schlüssel noch die Werte werden dabei geparsed, also durch den PHP-Interpreter bearbeitet. Leerzeichen am Anfang und am Ende eines Schlüssels werden entfernt. Der Schlüssel key3 im Beispiel wird als [key3] und nicht als [ key3 ] geführt. Wird kein Trennzeichen angegeben, so handelt es sich um einen Wert ohne Schlüssel. In diesem Fall wird die gesamte Zeile als Wert aufgefasst. Der Schlüssel wird in diesem Fall mit dem Wert gleichgesetzt. Auch hier werden Leerzeichen am Anfang und Ende des Schlüssels entfernt.

key1=value
key2:value

Wert ohne Schlüssel

 key3 = test

Soll im Wert eines Schlüssel-Wert-Paares ein Zeilenumbruch eingefügt werden, so muss dies über einen Schrägstrich am Ende der entsprechenden Zeile definiert werden.

# Mehrzeilige Eigenschaft mit \ als Trennzeichen
key4=Das ist die erste\
und das die zweite Zeile

Kommentare

Kommentare können durch ein # oder ein ! eingeleitet werden. Vor dem Prefix dürfen allerdings keine anderen Zeichen und auch keine Leerzeichen stehen.

#Das ist ein Dateikommentar.
#Nach dem Dateikommentar muss eine Leerzeile
! eingefügt werden.
 
# Das ist ein Kommentar
! das auch
 ! das ist kein Kommentar, sondern ein Wert ohne Schlüssel, da die Zeile mit einem Leerzeichen beginnt.

Ein Beispiel

Im folgendem Beispiel soll die nachfolgende Konfigurationsdatei eingelesen, geändert und wieder abgespeichert werden.

!Dateikommentar
#Das ist auch ein Dateikommentar
#Das sind properties

a = 1
b=1
c=1

#Kommentar
!Hinweis

d=Der Wert des Schlüssels d

#Test ohne Key
Eine schöne Stadt 

A=Das ist ein Schlüssel: mit Zeilenumbruch\
das ist die zweite zeile\
und die dritte
B=Neue Zeile

#Abschluß

Als erstes wird die Eigenschaftsdatei (file.properties), die im gleichen Verzeichnis liegt, eingelesen.

$properties = new Alvine\Types\Properties();
$stream = \Alvine\IO\FileInputStream::fromCurrentFilename('file.properties');
$properties->load($stream);

Im nächsten Schritt soll der Wert des Schlüssels d ausgegeben werden. Dies erfolgt über den Aufruf der Methode Properties::getProperty() wie im folgenden Beispiel zu sehen ist.

/**
 * Ausgabe des Wertes
 */
echo $properties->getProperty('d');

Das Setzen des Wertes ist genauso einfach über die Methode Properties::setProperty(). Das Speichern der Eigenschaft kann bequem über einen Output-Stream erfolgen.

$properties->setProperty('d', 'Neuer Wert');
/**
 * Speichern der Änderung
 */
$stream = \Alvine\IO\FileOutputStream::fromCurrentPath('file.properties');
$properties->save($stream);

Die neue Datei sieht damit folgendermaßen aus (zu beachten ist der Wert hinter dem d=).

!Dateikommentar
#Das ist auch ein Dateikommentar
#Das sind properties

a = 1
b=1
c=1

#Kommentar
!Hinweis

d=Neuer Wert

#Test ohne Key
Eine schöne Stadt 

A=Das ist ein Schlüssel: mit Zeilenumbruch\
das ist die zweite zeile\
und die dritte
B=Neue Zeile

#Abschluß

Collection

PHP stellt mit Arrays schon eine mächtiges Sprachelement zur Verfügung, mit denen verschiedene Aufgaben schnell und effizient umgesetzt werden können.

Erweiterung

Für die Verwaltung von Objekten stellt Alvine mit der Klasse Collection eine Erweiterung des Array-Konzeptes zur Verfügung. Mit der Übergabe eines Klassennamens beim Anlegen einer Kollektion, kann man diese Collection auf diesen Typ begrenzen. Beim Zuweisen eines anderen Typs wird eine TypeException geworfen.

$collection = new Alvine\Types\Collection('\Alvine\Types\Integer');
$collection->append(new \Alvine\Types\Integer(10));
$collection->append(new \Alvine\Types\StringType('HelloWorld'));  // TypeException() 

Über die mitgelieferte each-Funktion kann auf die einzelnen Objekte schnell zugegriffen werden.

$collection = new Alvine\Types\Collection('\Alvine\Types\Integer');
$collection->append(new \Alvine\Types\Integer(10));
$collection->append(new \Alvine\Types\Integer(20));
$sum = 0;
$collection->each(function($obj) use (&$sum) {
    $sum += $obj->integer; 
});
echo $sum; // 35

#Map

PHP stellt mit assoziativen Arrays ein mächtiges Sprachelement zur Verfügung, das in der Map-Implementierung von Alvine um einige Aspekte konkretisiert wurde. Maps sind assoziative Arrays, deren Schlüssel immer ein String sein muss.

Implementierung

Die Klasse Map stellt eine Reihe von Methoden zur Manipulation einer Map bereit. Daneben implementiert Map auch das Iterator-, ArrayAccess- und Countable-Interface. Daher kann der Zugriff neben den Methoden $map->getValue(1) auch über ein Array Zugriff $map[1] erfolgen.

Arbeiten mit Maps

Die Beispiele durchlaufen alle Einträge der Map und rufen die entsprechende Funktion auf. Besonders in Zusammenhang mit den anonymen Funktionen lassen sich so sehr effiziente Konvertierungen durchführen.

/**
 * Eine Map erstellen und mit einem Array inititalisieren
 **/
$map = new Alvine\Types\Map(array('a'=>'Auto','b'=>'Boot','c'->'Flugzeug'));
// An jeden Wert ... hängen
$map->each(function($k, $v) { return $v.'...'; });
/**
 * Es können neben einfachen Typen auch Objekte und sogar Lambdafunktionen
 * eingehängt werden. Diese müssen einen Rückgabewert enthalten.
 */
$map->ref = function($obj) { return 4; };
$value = $map->ref; // $value hat den Wert 4;
Im folgenden Beispiel soll nur auf bestimmte Schlüssel der Map zugegriffen werden.
/**
 * Die Callback-Funktion wird auf alle Schlüssel angewendet und 
 * im Ergebnis erhält jeder Wert drei Punkte angehängt: 
 * Auto... Boot... und Flugzeug...
 */
$map = new Alvine\types\Map(array('aaa'=>'Auto','bbb'=>'Boot','ccc'=>'Flugzeug'));
$map->each(function($k, $v) { return $v.'...'; });
/**
 * Über einen optionalen Filter können Einschränkungen auf den Schlüssel 
 * angewendet werden. Im folgendne Aufruf sollen nur Schlüssel die mit a
 * beginnen verwendet werden. Ergebnis: Auto...
 */
$map->each(function($k, $v) { return $v.'...'; }, 'a');

Maps mit hierarchischen Schlüssel filtern

Maps die als Schlüssel einen Wert haben (z.B. a.b.c.d) die durch einen Punkt getrennt sind, können mittels Map::match() gefiltert werden. Die einfachste Filterung ist ein exaktes übereinstimmen. Zusätzlich kann über ein Wildcard eine oder mehrere Stufen in der Filterung übersprungen werden.

/**
 * Mehrere Schlüssel/Wert-Paare sind über hierarchische Schlüssel definiert.
 */
$map = new Alvine\types\Map(('a.x.c.d'=>'Auto', 'a.b'=>'Boot', 'a.b.c.d'=>'Flugzeug', 'a.b.e.d'=>'Schiff', 'a.e.c.d'=>'Fahrrad', 'a.b.c.d.y'=>'Kanu'));
$intersec = $map->getIntersection('a.*.*.d');
/**
 * $intersec enthält nun             
 * [a.x.c.d] => Auto
 * [a.b.c.d] => Flugzeug
 * [a.b.e.d] => Schiff
 * [a.e.c.d] => Fahrrad
 * Das Kanu und das Boot sind nicht dabei
 */

Einfache Maps

Die Klasse SimpleMap ist eine Ableitung von Map und erlauben nur einfache Typen als Wert. Wird ein Objekt oder Array zugewiesen, so wird eine TypeException geworfen.

Maps für Objekte

Die Klasse ObjectMap stellt eine Map für Objekte zur Verfügung. Wird im Konstruktor ein Klassenname übergeben, so kann die Zuweisung auf diese Klassen beschränkt werden.

$map = new ObjectMap('\Alvine\Types\StringType');
$map['a'] = new \Alvine\Types\StringType('A'); // OK
$map['a'] = new \Alvine\Types\Integer(4); // TypeException

Über die Methoden ObjectMap::etObject($object) und ObjectMap::removeObject($object) können Objekte direkt - ohne Schlüssel - hinzugefügt bzw. entfernt werden.

$map = new ObjectMap();
$map->setObject($obj)
$map->removeObject($obj)

Die Methoden sind Hilfsmethoden für ObjectMap::setValue() und ObjectMap::remove(). Als Schlüssel $key wird jeweils die ID des Objektes verwendet.

Node

Die Node Klasse bietet die Verwaltung von Eltern-/Kindobjekten in einer klassischen Baumstruktur. Im wesentlichen besteht eine Node aus den eigenen Daten und einer Liste der Kinder.

Eine Node hat einen Namen und einen Wert.

// Neue Node erstellen
$node=new \Alvine\Types\Node('nodeName', 'nodeValue');

echo "Name: ".$node->getName()." // Wert: ".$node->getValue()."\n";
// -> Name: nodeName // Wert: nodeValue

Manipulation der Kinder

Einer Node können Kinder hinzugefügt werden.

// Neue Node erstellen
$node=new \Alvine\Types\Node('test', 'content');

// Kinder hinzufügen
$node->appendChild(new \Alvine\Types\Node('child1', '1'));
$node->appendChild(new \Alvine\Types\Node('child2', '2'));
$node->appendChild(new \Alvine\Types\Node('child3', '3'));

Über verschiedene Funktionen kann die Liste der Kinder manipuliert werden. Dazu gehören neben dem Hinzufügen Node::appendChild(), das Auslesen Node::getChildren(), das Ersetzen von Nodes Node::replaceChildren() auch das Löschen von Nodes Node::removeChild().

Mittels Node::truncate() werden alle Kinder entfernt.

Zugriff auf die Kinder

Auf die Nodes kann mit der funktion Node::getChildren($name) zugegriffen werden. Über den Parameter $name kann eine Einschränkung auf bestimmte Tags gemacht werden.

// Neue Node erstellen
$node=new \Alvine\Types\Node('test', 'content');

// Kinder hinzufügen
$node->appendChild(new \Alvine\Types\Node('childA', '1'));
$node->appendChild(new \Alvine\Types\Node('childB', '2'));
$node->appendChild(new \Alvine\Types\Node('childC', '3'));
$node->appendChild(new \Alvine\Types\Node('childC', '4'));
$node->appendChild(new \Alvine\Types\Node('childD', '5'));

// Alle Nodes mit childC selektieren
$nodes=$node->getChildren('childC');
foreach($nodes AS $node) {
    echo (string) $node."\n";
}
// -> 3
// -> 4

Alternativer Zugriff

Über die in PHP implementierten magische Setter __set- und Getter __get-Methode kann direkt auf ein Kind zugegriffen werden.

// Neue Node erstellen
$node=new \Alvine\Types\Node('test', 'content');

// Kinder hinzufügen
$node->appendChild(new \Alvine\Types\Node('child1', '1'));
$node->appendChild(new \Alvine\Types\Node('child2', '2'));
$node->appendChild(new \Alvine\Types\Node('child3', '3'));


echo $node->child1;
// -> 1

echo $node->child3;
// -> 3

Iteratoren

Die Node-Klasse implementiert neben dem ArrayAccess-Interface auch die folgenden Iterator-Interfaces: \Iterator, \RecursiveIterator, \OuterIterator. Damit lässt sich einfach auf die Baumstruktur zugreifen.

In dem folgenden Beispiel wird eine Baumstruktur aufgebaut und mittels einem rekursiven Iterator durch die Struktur gelaufen.

/**
 * Gegebene Node-Struktur
 * 
 * node1
 *  ├─ node2
 *  │    ├─ node3
 *  │    │    └─ node4
 *  │    │         ├─ node6
 *  │    │         └─ node7
 *  │    └─ node5
 *  ├─ node8
 *  ├─ node9
 **/


// Erstellen der einzelnen Node-Objekte
$n1=new \Alvine\Types\Node('node1', 'i1');
$n2=new \Alvine\Types\Node('node2', 'i2');
$n3=new \Alvine\Types\Node('node3', 'i3');
$n4=new \Alvine\Types\Node('node4', 'i4');
$n5=new \Alvine\Types\Node('node5', 'i5');
$n6=new \Alvine\Types\Node('node6', 'i6');
$n7=new \Alvine\Types\Node('node7', 'i7');
$n8=new \Alvine\Types\Node('node8', 'i8');
$n9=new \Alvine\Types\Node('node9', 'i9');

// Aufbau der Baumstruktur
$n1->appendChild(
        $n2->appendChild(
            $n3->appendChild(
                $n4->appendChild($n6)
                ->appendChild($n7)))
        ->appendChild($n5))
    ->appendChild($n8)
    ->appendChild($n9);

// Erstellen des Iterator
$iteratorIterator=new \RecursiveIteratorIterator(
    $n1->getChildren(),
    \RecursiveIteratorIterator::SELF_FIRST,
    \RecursiveIteratorIterator::CATCH_GET_CHILD);

// Iterator durchlaufen
foreach($iteratorIterator as $node) {
    echo (string) \str_pad($node->getName(), 5+$node->getLevel(), '.', \STR_PAD_LEFT)." (Level: ".$node->getLevel().") \n";
}

/**
 * ->
 * .node2 (Level: 1) 
 * ..node3 (Level: 2) 
 * ...node4 (Level: 3) 
 * ....node6 (Level: 4) 
 * ....node7 (Level: 4) 
 * ..node5 (Level: 2) 
 * .node8 (Level: 1) 
 * .node9 (Level: 1)
 */

Nodes suchen

Über Constraints lassen sich die Kinder einer Node durchsuchen. Das Ergebnis der Suche ist eine (nodelist.md)[NodeList].

// Neue Node erstellen
$node=new \Alvine\Types\Node('test', 'content');

$div=new Alvine\Types\Node('id1', 'value1');
$div->appendChild(new Alvine\Types\Node('id2', 'value2'));
$div->appendChild(new Alvine\Types\Node('id3', 'value3'));
$div->appendChild(new Alvine\Types\Node('id3', 'value4'));

// Suche nach einem Namen
$find1=$div->find(new Alvine\Types\Node\Constraint\Name('id3'));
foreach($find1 AS $n) {
    echo $n->getName()." :: ".$n->getValue()." \n";
}
// -> id3 :: value3 
// -> id3 :: value4 

// Suche nach einem Wert und einem Namen
$find2=$div->find(new \Alvine\Util\Constraint\AndOperator(
        new \Alvine\Types\Node\Constraint\Name('id3'),
        new \Alvine\Types\Node\Constraint\Value('value4')
    ));
foreach($find2 AS $n) {
    echo $n->getName()." :: ".$n->getValue()." \n";
}
// -> id3 :: value4 

// Suche nach einem Wert alleine
$find3=$div->find(new \Alvine\Types\Node\Constraint\Value('value2'));
foreach($find3 AS $n) {
    echo $n->getName()." :: ".$n->getValue()." \n";
}
// -> id2 :: value2

NodeList

Die NodeList Klasse bietet die Verwaltung mehrerer Nodes in einer Liste.

In dem folgendem Beispiel wird in einer NodeList gesucht.

$nodeList = new \Alvine\Types\NodeList();
$nodeList->appendNode(new \Alvine\Types\Node('nodeName1', 'nodeValue1'));
$nodeList->appendNode(new \Alvine\Types\Node('nodeName2', 'nodeValue2'));

Key-Value

Die Klasse \Alvine\Types\KeyValue stellt eine einfache Möglichkeit zur Speicherung von Schlüssel-Wert-Paaren bereit.

$keyValue = new \Alvine\Types\KeyValue('myKey', 'myValue');

Zeichenkette zerlegen

Über die Methode \Alvine\Types\KeyValue::getInstanceFromString($string, $separator) lässt sich eine Zeichenkette anhand des Trennzeichens zerlegen.

$kv=\Alvine\Types\KeyValue::getInstanceFromString('a:20');
echo $kv->getValue();
// → 20

Besitzt die Zeichenkette ein anderes Trennzeichen als den Standardwert :, so kann dieses Trennzeichen als zweiter Parameter übergeben werden.

$kv=\Alvine\Types\KeyValue::getInstanceFromString('a=20','=');
echo $kv->getValue();
// → 20

Wird das Trennzeichen nicht gefunden, so wird eine Exception geworfen.

$kv=\Alvine\Types\KeyValue::getInstanceFromString('a');
// → Alvine\Core\ParseException

Immutable

Über das Interface \Alvine\Types\Immutable können Klassen als unveränderlich gekennzeichnet werden. Dadurch kann schon im Vorfeld überprüft werden, ob das Objekt veränderbar ist.

class myClass implements \Alvine\Types\Immutable {

}

Möchte man eine Klasse explizit als veränderbar kennzeichnen, so kann man das über das \Alvine\Types\Mutable machen.

class myClass implements \Alvine\Types\Mutable {

}

Note

Klassen dürfen nur eines der beiden Interfaces implementieren und nicht beide. Folgendes führt zu einem Fehler: class A implements Mutable, Immutable {


</div>
</div>




MediaTypes

Die Klasse \Alvine\Types\MediaTypes definiert einen Inhaltstyp nach der MIME-Spezifikation. Es kann entweder der Type und Subtype getrennt oder als ein Parameter übergeben werden.

Eine Liste der gültigen Mimetypes ist hier zu finden.

Der optionale Parameter $quality legt eine Gewichtung des Medientyps zu anderen Mediatype fest. Weitere Parameter können zum Beispiel der DOM-Level bei HTML sein.

// Neuer Multipart-Typ
$form=new Alvine\Types\Mime\Multipart();

$uuid=(string) \Alvine\Util\UUID::generateFromRandom();
$href='cid:1.urn:uuid:'.$uuid;

$part1Part=new \Alvine\Types\Mime\Text((string) 'Erster Inhalt');

// Header des Part1 Elements 
$part1Header=(new \Alvine\Net\Http\RequestHeader())
    ->setValue('Content-Type', 'application/xop+xml; charset=UTF-8; type="text/xml"')
    ->setValue('Content-Transfer-Encoding', 'binary')
    ->setValue('Content-ID', '<0.urn:uuid:'.$uuid.'>');

$part2Part=new \Alvine\Types\Mime\Text('Zweiter Inhalt');

// Header des Part2 Elements 
$part2Header=(new \Alvine\Net\Http\RequestHeader())->setValue('Content-Type', 'application/octet-stream')
        ->setValue('Content-Transfer-Encoding', 'binary')->setValue('Content-ID', '<1.urn:uuid:'.$uuid.'>');

// Parts hinzufügen 
$form->addPart($part1Part, $part1Header);
$form->addPart($part2Part, $part2Header);
$body=new \Alvine\Net\Http\Body($form);
echo (string) $body;

Für die verschiedenen Haupttypen stehen eigene Klassen zur Verfügung:

  • \Alvine\Types\Mime\Application
  • \Alvine\Types\Mime\Audi
  • \Alvine\Types\Mime\Image
  • \Alvine\Types\Mime\Message
  • \Alvine\Types\Mime\Model
  • \Alvine\Types\Mime\Text
  • \Alvine\Types\Mime\Video

Diese Klassen definieren im wesentlichen die Untertype als Konstanten.

Multipart

Diese Klasse \Alvine\Types\Mime\Multipart bildet einen aus einem oder mehreren Typen zusammengesetzten Inhaltstyp ab.

In dem Beispiel werden unterschiedliche Daten per Multipart-Body an den Server übergeben.

// Code fehlt noch

Dieser Code erzeugt folgende Ausgabe

--AlvineBoundaryb06bfd1d
Content-Type: application/xop+xml; charset=UTF-8; type="text/xml"
Content-Transfer-Encoding: binary
Content-ID: <0.urn:uuid:0600b9ab-97da-42f5-f9ee-06945301a276>

Erster Inhalt
--AlvineBoundaryb06bfd1d
Content-Type: application/octet-stream
Content-Transfer-Encoding: binary
Content-ID: <1.urn:uuid:0600b9ab-97da-42f5-f9ee-06945301a276>

Zweiter Inhalt
--AlvineBoundaryb06bfd1d--

Parameter

Die abstrakte Klasse \Alvine\Types\Parameter erweitert die KeyValue-Klasse um die zusätzliche Eigenschaft required.

Es gibt für unterschiedliche Typen Parameter.

  • \Alvine\Types\Parameter\ArrayType
  • \Alvine\Types\Parameter\Boolean
  • \Alvine\Types\Parameter\Collection
  • \Alvine\Types\Parameter\Integer
  • \Alvine\Types\Parameter\Map
  • \Alvine\Types\Parameter\Mixed
  • \Alvine\Types\Parameter\ObjectType
  • \Alvine\Types\Parameter\Simple
  • \Alvine\Types\Parameter\StringType

Beispiel:

// Integer Parameter
$parameter=new \Alvine\Types\Parameter\Integer('myKey', 2);
echo (string) $parameter;
// → myKey:2

// Array Parameter
$parameter=new \Alvine\Types\Parameter\ArrayType('myKey', ['a', 'b']);
echo (string) $parameter;
// → myKey:0:a,1:b

// Array Parameter
$parameter=new \Alvine\Types\Parameter\Map('myKey', new \Alvine\Types\Map(['a'=>1, 'b'=>2]));
echo (string) $parameter;
// → myKey🅰️1,b:2

Über die Methode \Alvine\Types\Parameter::getInstanceFromString($string, $separator) lässt sich eine Zeichenkette anhand des Trennzeichens in einen Parameter zerlegen.

Stack

Eine Stack ist eine Stapel von Werten nach dem LIFO-Prinzip1 verarbeitet werden.

Über die Methoden \Alvine\Types\Stack::isEmpty(), \Alvine\Types\Stack::peek(), \Alvine\Types\Stack::pop(), \Alvine\Types\Stack::count() und \Alvine\Types\Stack::push($object) wird der Stack verwaltet.

$stack = new \Alvine\Types\Stack();
$stack->push(new \Alvine\Types\StringType('Value 1'));
$stack->push(new \Alvine\Types\StringType('Value 2'));
$stack->push(new \Alvine\Types\StringType('Value 3'));

while(!$stack->isEmpty()) {
    echo $stack->pop()."\n";
}

// → Value 2
//   Value 2
//   Value 1

Die Methode \Alvine\Types\Stack::peek() holt den nächsten Wert vom Stack, lässt aber anders als die Methode \Alvine\Types\Stack::pop() den Wert auf dem Stapel.

$stack = new \Alvine\Types\Stack();
$stack->push(new \Alvine\Types\StringType('Value 1'));
$stack->push(new \Alvine\Types\StringType('Value 2'));
$stack->push(new \Alvine\Types\StringType('Value 3'));

echo $stack->peek();
echo $stack->peek();
echo $stack->peek();
// → Value 3
//   Value 3
//   Value 3

Queue

Eine Queue ist eine Warteschlange in der Werte nach dem FIFO-Prinzip1 verarbeitet werden.

Über die Methoden \Alvine\Types\Queue::isEmpty(), \Alvine\Types\Queue::peek(), \Alvine\Types\Queue::poll() und \Alvine\Types\Queue::push($object) wird die Warteschlange verwaltet.

$queue = new \Alvine\Types\Queue();
$queue->push(new \Alvine\Types\StringType('Value 1'));
$queue->push(new \Alvine\Types\StringType('Value 2'));
$queue->push(new \Alvine\Types\StringType('Value 3'));

while(!$queue->isEmpty()) {
    echo $queue->poll()."\n";
}

// → Value 1
//   Value 2
//   Value 3

Die Methode \Alvine\Types\Queue::peek() holt den nächsten Wert in der Queue, lässt aber anders als die Methode \Alvine\Types\Queue::poll() den Wert in der Queue.

$queue = new \Alvine\Types\Queue();
$queue->push(new \Alvine\Types\StringType('Value 1'));
$queue->push(new \Alvine\Types\StringType('Value 2'));
$queue->push(new \Alvine\Types\StringType('Value 3'));

echo $queue->peek();
echo $queue->peek();
echo $queue->peek();
// → Value 1
//   Value 1
//   Value 1

Subset

Ein Subset bildet eine Teilmenge ab und kann zum Beispiel für die Paginierung von Datensätzen verwendet werden.

// Teilmenge
$objects=new \Alvine\Types\Collection;
$objects->append(new \Alvine\Types\StringType('value1'));
$objects->append(new \Alvine\Types\StringType('value2'));
$objects->append(new \Alvine\Types\StringType('value3'));

// Offset an dem diese Teilmenge in der Gesamtmenge steht.
$offset=10;

// Gesamtanzahl der Einträge
$totalNumber=100;

// Einträge pro Subset
$objectsPerSubset=10;

$subset=new \Alvine\Types\Subset($objects, $offset, $totalNumber, $objectsPerSubset);

echo (string) $subset->getCurrentPage();
// → 2

echo (string) $subset->getNextOffset();
// → 20

echo (string) $subset->getPages();
// → 10

echo (string) \implode(',', $subset->getOffsets());
// → 0,10,20,30,40,50,60,70,80,90

Reguläre Ausdrücke

PHP gibt, je nach Konfiguration des error-reporting bei einem fehlerhaften Pattern eine PHP-Warnung aus.

\preg_match('/wrong pattern', 'test');
// → PHP Warning:  preg_match(): No ending delimiter '/' found in

Mit Hilfe der Klasse \Alvine\Types\RegularExpression wird die Warnung gekapselt und die Gültigkeit des regulären Ausdruck kann sicher geprüft werden. Im Falle eines ungültigen Ausdruckes wird eine Exception geworfen, die über try/catch behandelt werden kann.

try {
    $regex=new \Alvine\Types\RegularExpression('/[a-z]+/');
    echo (string) $regex;
    // → /[a-z]+/
} catch(\Alvine\Types\RegularExpressionException $e) {
    
}

Konfiguration

Die Klasse \Alvine\Types\Configuration erweiter die Klasse \Alvine\Types\HierarchicalProperties um die Möglichkeit die Daten aus einer Datei einzulesen.

Die Klasse \Alvine\Types\Configuration\Ini liest eine Ini-Datei ein und legt diese in einer Map-Struktur ab.

Vergleich von Objekten

Das Interface \Alvine\Types\Comparable definiert die Methode \Alvine\Types\Comparable::compareTo(), dass anders als die Methode \Alvine\Core\Alvine::euals() einen gerichteten Vergleich durchführt.

Gerichteter Vergleich von $this mit $x gibt 0 zurück, wenn das Objekt und der Parameter identisch sind. 1 falls das Objekt größer ist und -1, wenn das Objekt kleiner ist. Es sollte folgendes gelten (x.compareTo(y)==0) == (x.equals(y))

JSON

Die Hilfsklasse \Alvine\Types\JsonConverter stellt Methoden für die Arbeit mit JSON-Objekte bereit.

Die Methode \Alvine\Types\JsonConverter::from($value) prüft der Typ von $value und gibt eine entsprechende JSON-Zeichenkette zurück.

\Alvine\Types\JsonConverter::fromObject($value) und \Alvine\Types\JsonConverter::fromArray($value) wandeln Alvine-Objekte bzw. Arrays entsprechend um.

Bei Alvine-Objekten wird geprüft, ob diese eine der Methoden asArray, toArray, asJson oder __toString implementiert haben. Wird eine Methode gefunden, so wird diese für die Umwandlung verwendet.

Übersicht

Das Framework bietet mit den Klassen aus dem XML Namensraum eine für Alvine angepassten Umgang mit XML-Objekten

Inhalt

Dokumente

Die \Alvine\Xml\Document-Klasse erweitert die Node-Klasse um Funktionen für den Umgang mit einem vollständigem Dokument.

In dem ersten Beispiel wird ein XML-Dokument aus einer Zeichenkette erstellt.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<entries>
  <entry>Inhalt1</entry>
  <entry>Inhalt2</entry>
  <entry>Inhalt3</entry>
</entries>
// Zeichenkette (siehe oben) in XML umwandeln
$doc=new \Alvine\Xml\Document($xml);

// Erste Ebene (entries-Tag)
$entries=$doc->getChildren();

// Kinder des entries-Tag
$listOfEntry=$entries->getChildren();

// Durch die entry-Tags iterieren
foreach($listOfEntry AS $entry) {
    echo (string) $entry->getValue()."\n";
}

// -> Inhalt1
// -> Inhalt2
// -> Inhalt3

Wie das folgende Beispiel zeigt, lassen sich Über die Methode

\Alvine\Xml\Document::getInstanceFromURI($uri)

XML-Dokumente auch direkt über eine URL erstellen.

// URL des XML-Dokuments
$uri = 'https://www.currency-iso.org/dam/downloads/lists/list_one.xml';
$uriObject=new \Alvine\Net\Resource\URI($uri);

// Dokument laden und erstellen
$doc=\Alvine\Xml\Document::getInstanceFromURI($uriObject);

// Ausgabe des Dokumentes
echo (string) $doc;

Das Ergebnis ist folgender XML-Schnipsel

<?xml version="1.0" encoding="UTF-8"?>
  <ISO_4217 Pblshd="2018-08-29">
     <CcyTbl>...

(Auszugsweise dargestellt)

Node

Die \Alvine\Xml\Node-Klasse erweiter die \Alvine\Types\Node-Klasse um Funktionen für den Umgang mit XML-Strukturen.

// Eltern-Node
$obj=new \Alvine\Xml\Node('parent');

// Kind hinzufügen
$obj->appendChild(new \Alvine\Xml\Node('child'));

// Über den Tagnamen erhält man eine Referenz 
// auf das Objekt ($obj->parent) ist identisch zu $obj)
echo (string) $obj->parent;
// -> <parent>
// ->  <child/>
// -> </parent>

// Zugriff auf die Kinder liefert NodeList 
// zurück (beide Aufrufe sind gleichbedeutend)
echo $obj->parent->child;
// -> <child/>

echo $obj->child;
// -> <child/>

// auf die XML-Zeichenkette kann mit 
// dem Schlüsselwort xml zugegriffen werden.
echo $obj->xml;
// <parent>
// <child/>
//</parent>

Eine XML-Node besitzt einen der folgenden Typen:

KonstanteWertBeschreibung
NODE0Standardnode
ELEMENT1Element
DOCUMENT2XML-Dokument

Attribuite

Attribute im XML-Tag können über die Methode

\Alvine\Xml\Node::setAttributeValue($name, $value)

gesetzt und geändert werden. Mittels der Funktion

\Alvine\Xml\Node::removeAttribute($name)

kann ein Attribute entfernt werden.

// Eltern-Node
$obj=new \Alvine\Xml\Node('parent');

$obj->setAttributeValue('attributeA', '1');
$obj->setAttributeValue('attributeB', '2');

echo (string)$obj;
// -> <parent attributeA="1" attributeB="2"/>


$obj->removeAttribute('attributeB');

echo (string)$obj;
// -> <parent attributeA="1"/>

Alle Attribute lassen sich über die Methode \Alvine\Xml\Node::getAttributes() auslesen. Das Ergebnis ist ein Objekt vom Typ \Alvine\Xml\AttributeMap.

Parser

Der XML-Parser ist für die Übersetzung eines XML-Dokumentes in Objektstruktur verantwortlich. In dem folgenden Beispiel wird die XML Zeichenkette in XML-Objekte umgewandelt. Jeder Knoten ist dabei von Typ \Alvine\Xml\Element.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<verzeichnis>
     <titel>Wikipedia Städteverzeichnis</titel>
     <eintrag>
          <stichwort>Genf</stichwort>
          <eintragstext>Genf ist der Sitz von ...</eintragstext>
     </eintrag>
     <eintrag>
          <stichwort>Köln</stichwort>
          <eintragstext>Köln ist eine Stadt, die ...</eintragstext>
     </eintrag>
</verzeichnis>
$parser=new \Alvine\Xml\Parser;
$doc=$parser->parseXML($xml);  // siehe oben


$node=$doc->getChildren()->getChildren()
    ->find(new \Alvine\Types\Node\Constraint\Name('stichwort'))
    ->current();

echo $node->getClass();
// -> \Alvine\Xml\Element

Um einzelnen Elemente eine besondere Bedeutung zu geben, kann dem Parser ein Suchnamensraum übergeben werden. In diesem Beispiel hat die Node nicht mehr den Typ \Alvine\Xml\Element, sondern \My\Name\Space\Stichwort.

Dadurch kann ein XML-Dokument mit Semantik erweitert werden.

namespace My\Name\Space;

class Stichwort extends \Alvine\Xml\Element {
    
}

Diese Klasse wird nun beim Parsen einer XML Struktur verwendet. Dabei wird in den zum Parser hinzugefügten Namespaces nach einer Klasse mit dem Namen des Tag gesucht. Im Falle dieses Dokuments wird nach den folgenden Klassen gesucht.

  • My\Name\Space\Verzeichnis
  • My\Name\Space\Titel
  • My\Name\Space\Eintrag
  • My\Name\Space\Stichwort
  • My\Name\Space\Eintragstext

Da in diesem Beispiel nur die Klasse \My\Name\Space\Stichwort definiert wurde, sind, bis auf die Stichwort-Node, alle anderen Nodes vom Type \Alvine\Xml\Element.

$parser=new \Alvine\Xml\Parser;
$parser->addSearchNamespace('\\My\\Name\\Space');
$doc=$parser->parseXML($xml);

$node=$doc->getChildren()->getChildren()
    ->find(new \Alvine\Types\Node\Constraint\Name('stichwort'))->current();
echo $node->getClass();
// -> \My\Name\Space\Stichwort

Die Stichwort-Node ist vom Typ \My\Name\Space\Stichwort.

Über diesen Mechanismuss lassen sich verschiedene Logiken abbilden.

XPath

Die XML Path Language XPath ist eine Abfragesprache, um Teile eines XML-Dokumentes zu adressieren und auszuwerten. Die Spezifikation kann beim W3C nachgelesen werden. Mit Hilfe von XPath kann so auf einzelne Nodes zugegriffen werden.

Mit diesem Tool lassen sich abfragen schnell testen.

Als Beispiel dient folgende XML-Struktur.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<verzeichnis>
  <titel>Wikipedia Städteverzeichnis</titel>
    <eintrag id="14">
      <stichwort>Genf</stichwort>
      <eintragstext>Genf ist der Sitz von ...</eintragstext>
    </eintrag>
    <eintrag id="19">
      <stichwort>Köln</stichwort>
      <eintragstext>Köln ist eine Stadt, die ...</eintragstext>
    </eintrag>
</verzeichnis>

Auf einen einzelnen Eintrag soll nun mittels XPath zugegriffen werden.

$document=new \Alvine\Xml\Document($xml);

// Xpath-Objekt erstellen
$xPath=new \Alvine\Xml\XPath($document->getFirstChild());

// Alle Einträge mit der ID=14 zurückgeben
$result=$xPath->query('/verzeichnis/eintrag[@id=14]');

echo (string) $result;

Das Ergebnis ist folgender XML Schnipsel

<eintrag id="14">
  <stichwort>Genf</stichwort>
  <eintragstext>Genf ist der Sitz von ...</eintragstext>
</eintrag>

Unique Identifier (UUID)

Der Universally Unique Identifier (UUID) wird auch zur Objektidentifizierung verwendet. Alvine ist so konzipiert, das alle Stamm-, Hilfs und Transaktionsdaten eindeutig und Systemübergreifend identifiziert und ausgetauscht werden können. So ist es möglich einen Datensatz aus dem System A ohne größere Probleme in das System B zu übertragen. Zur Identifizierung kommt die in der Spezifikation RFC 4122 beschriebenen UUID zum Einsatz.

Der Aufbau einer UUID ist im RFC 4122 genau beschrieben. Eine Beispiel UUID sieht folgendermassen aus und repräsentiert einen 16 Beispiel UUID 550e8400-e29b-11d4-a716-446655440000

Die UUID kann auch als URN definiert werden: urn:uuid:550e8400-e29b-11d4-a716-446655440000

echo \Alvine\Util\UUID::generateFromRandom();
// Ergebnis -> zufällige UUID

echo \Alvine\Util\UUID::generateFromName('myname', 'name');
// Ergebnis -> 9ced7028-3599-573c-90ef-9590f243b9b3

echo \Alvine\Util\UUID::generateFromTime();
// Ergebnis -> UUID abhängig von Zeit

Speicherung in der Datenbank

Die Speicherung in der Datenbank sollte nicht als String, sondern als (z.B. in MySQL) einem binary (16) gespeichert werden. Verschiedene Artikel belegen darüber hinaus, das die Verwendung der UUID als Primärschlüssel sehr teuer (langsam) ist.

UNHEX(REPLACE(UUID(), '-',''))
 
CREATE TABLE test (
id varbinary(16) not null,
name varchar(15) not null,
PRIMARY KEY(id)
);

Change Log

Alle erwähnenswerten Änderungen in der Komponente werden hier dokumentiert.

[Unreleased] 2023-11-23

1.46.0 - 2023-10-05

Hinzugefügt

  • neuer Mailpart für Bilder application/source/net/mail/MailPartImage.class.php

1.45.1 - 2022-11-11

Geändert

  • Transformer empty prüfung umgestellt auf === ‘’ application/source/data/Transformer.class.php

1.45.0 - 2022-11-11

Hinzugefügt

-neue Klasse Transformer application/source/data/Transformer.class.php

1.44.0 - 2022-07-29

Geändert

neuen rollbackStackCounter , verhindert das ein Commit gemacht werden kann wenn innerhalb einer Transaktion ein rollback voher schon ausgeführt wurde application/source/persistence/relation/DataObject.class.php

Behoben

in der Methode rollbackTransaction muss geprüft werden ob der transactionStackCounter>0 , nur dann rausspringen application/source/persistence/relation/DataObject.class.php

Hinzugefügt

application/source/persistence/CommitNotAllowedException.class.php application/source/persistence/NoOpenRollbackAvailableException.class.php

1.43.0 - 2022-07-11

Geändert

neue Methode getSubject application/source/net/mail/Mail.class.php

1.42.0 - 2022-01-30

Hinzugefügt

Neue Klasse \Alvine\Persistence\Relation\SQL\Operation\NotIn

1.41.0 - 2021-11-12

Geändert

Methode isLimitToClass und checkLimitation um einen Parameter $subclass erweitert um auf auch Unterklassen prüfen zu können /source/types/Collection.class.php

1.40.0 - 2021-11-03

Hinzugefügt

  • Erweiterung um die Cookieoption samesite
  • CORS Funktionen und entsprechende neue Header

Geändert

  • SQL Fehlermeldung besser herausgeben
  • Typen hinzugefügt und kleinere Ungenauigkeiten beseitigt

Behoben

  • Komponentenpfad wird nicht korrekt aufgebaut in der Funktion getClassPath der Klasse \Alvine\Core\ComponentLoader

1.39.0 - 2021-10-13

Geändert

  • neue Methoden merge und reset /source/data/ValidationReport.class.php

1.38.0 - 2021-09-02

Geändert

  • ValidationReport Namespace umgezogen zu Alvine\Data\ValidationReport \Alvine\Persistence\Model\ValidationReport ist nun Deprecated

1.37.0 - 2021-08-05

Geändert

  • getReplaceString in eine Methode ausgelagert /source/text/MessageFormatter.class.php

Hinzugefügt

  • MessageHierachicalFormatter /source/text/MessageHierachicalFormatter.class.php

1.36.0 - 2021-07-25

Hinzugefügt

  • neue Klasse Operators für das zusammen fassen von mehreren Operators /source/util/constraint/Operators.class.php
  • neue Klasse für ODER operationen /source/util/constraint/OrOperators.class.php
  • neue Klasse für UND operationen /source/util/constraint/AndOperators.class.php

1.35.0 - 2021-03-12

Geändert

  • Copyright hinweise und Tests für Amount
  • Umstellung des Build-Prozesses auf Make und das neue Build-Phar
  • Fehlermeldung verbessert
  • Übersetzungen überarbeitet

Behoben

  • Die Komponente lässt sich nicht bauen und der Upload geht in das falsche Verzeichnis
  • Die Fehlermeldung bei XML-Parser lässt keine Rückschlüsse auf die Datei
  • Überflüssige Zeile file_put_contents entfernt

1.34.0 - 2020-12-16

Geändert

  • beim Inludieren von MockProviderForObjectStorageUpdate hat MockProviderForObjectStorage gefehlt, die Reihenfolge des Includes ist nicht gesichert
  • Alvine\Measure\Amount Berechnungsfunktionen geändert , immer wenn newInstance gemacht wird, wird static:: verwendet

1.33.0 - 2020-11-04

Hinzugefügt

  • neue Abhängigkeitstabelle der unterstützten PHP Versionen
  • neue Abfragen für Ländercodes Alpha3/Alpha2

Geändert

  • Anpassungen für die Umstellung auf PHPUnit9 und PHP8

Behoben

  • Korrekturen im Serialize-Interface
  • Serialisierung von Nicht-ISO-Zeichen
  • Kompatibilitätsanpassungen am Script checkRequirements.php
  • SerializableImplementation angepasst , static Properties werden nun nicht mehr serialisiert

1.32.0 - 2020-10-07

Behoben

  • Funktionalität des Transaktionszählers behoben

1.31.0 - 2020-08-28

Hinzugefügt

  • Neue Regel zum Prüfen ob eine Funktion verfügbar ist

Geändert

  • Verbesserungen der URI-Klasse
  • Verbesserung des URL-Testings
  • Umstellung der \Alvine\Core\Component Methode
  • Wenn Das Modul Intl nicht installiert ist kommt es zu ParseErrors
  • Englische Texte/Fehlermeldungen hinzufügen

Behoben

  • PHP7.4 liefert für den MIME-Type einer leeren Datei stat inode/x-empty den Wert application/x-empty

1.30.0 - 2020-06-25

Hinzugefügt

  • Neue Funktionalität eines Transaktionszählers

Geändert

  • die Beschreibung von append angepasst
  • Erweiterung der Phar Erstellung um die Möglichkeit Dateien oder Verzeichnisse auszuschließen

1.29.0 - 2020-06-16

Geändert

  • [ALE-717] Dokumentation der Messwerte
  • [ALE-718] Methoden um Objekte in der Objektmap setzen zu können
  • [ALE-719] Überarbeitung der Währungsklassen über Script
  • [ALE-720] Anpassungen an der Dokumentation, hinzufügen von Typen, Anpassungen Measurement

1.28.0 - 2020-06-02

Hinzugefügt

  • [ALE-710] Implementierung von Mutable und Imutable siehe Gitlab-Issue #6

Geändert

  • [ALE-703] Umstellung auf Jekyll
  • [ALE-704] - [ALE-709] Anpassungen an der bootstrap.inc.php
  • [ALE-711] Documentation Mutable und Imutable
  • [ALE-712] Collection und ObjectMap auf limitToClass \Alvine\Types\ClassType::getNormalizedName umstellen
  • [ALE-714] Measurement mit Rückgabewerten ausstatten

Behoben

  • [ALE-715] Alvine\Measure\SI\StandardModel Error: Call to a member function equals() on string
  • [ALE-716] BUGFIX für falschen Rückgabewert

1.27.0 - 2020-04-06

Geändert

  • [ALE-701] Collection auf getID() umbauen

1.26.0 - 2020-02-05

Hinzugefügt

  • [ALE-680] Neue Klasse XAlvineHintField
  • [ALE-681] neues Interface Alvine\Core\InstantiableFromMap
  • [ALE-686] Neue Klasse XRealIpField
  • [ALE-694] eigene Methode Component::getResourceText um an die Localen zu kommen
  • [ALE-695] Neue Methode Directory::getLastChilds

Geändert

  • [ALE-677] Componentenklasse strict setzen und Returnvalues definieren.
  • [ALE-679] Änderung der Komponente; Die Methoden install und deinstall wurden entfernt
  • [ALE-688] Erweiterung der Fields um Methoden zur Stingerzeugung
  • [ALE-689] GenericStatements können nun auch mit Maps umgehen
  • [ALE-691] SQLFunctions vereinfachen (eine Funktion für das Setzen des Status)
  • [ALE-692] Erweiterung der Definitionen um Expressions
  • [ALE-693] Das initialisierne des Encodings muss bei jedem Aufruf passieren

Behoben

  • [ALE-690] BUGFIX: GenericStatements können nun auch mit Maps umgehen

1.25.0 - 2019-10-15

Hinzugefügt

  • [ALE-670] Neuer Header für Cloudflare Standard
  • [ALE-673] Neue Interfaces InstantiableFromJson und InstantiableFromArray
  • [ALE-674] Neue Interfaces InstantiableFromArrayHelper

Geändert

  • [ALE-664] Typo in Funktion File::setDirectory
  • [ALE-669] Typedeklaration für die Klasse Basic

Behoben

  • [ALE-671] Wird eine URI ohne Pfad angegeben, so kommt es zu einem 403.
  • [ALE-672] Relative URI ohne Domain wurden falsch geparsed eq: /de/home

1.24.0 - 2019-09-12

Geändert

  • [ALE-664] Typo in Funktion File::setDirectory

1.23.0 - 2019-09-12

Geändert

  • [ALE-659] Dokumentation und Typhinting in der Funktion Alvine::getShortID

Behoben

Beim Absenden einer E-Mail muss EHLO und HELO den eigenen Server mitgeben

1.22.0 - 2019-07-08

Geändert

  • [ALE-645] Stringumwandlung von Map; ArrayHelper und KeyValue verbessert
  • [ALE-646] Erweiterung der Dokumentation im Bereich der Typen
  • [ALE-647] Überarbeitung verschiedener Typen

Behoben

  • [ALE-644] Der Methode StringType::encode() hat der zweite Parameter gefehlt

1.21.0 - 2019-07-04

Behoben

  • [ALE-642] \Alvine\Util\Logging\Handler\FileTest::testNoDirectoryException schlägt fehl

1.20.0 - 2019-06-11

Geändert

  • [ALE-639] Speicherung von hierarchischen Passwörtern implementieren
  • [ALE-640] __toString für FileInputStreams implementiert

1.19.0 - 2019-04-11

Hinzugefügt

  • [ALE-634] Neue Klasse \Alvine\Net\Http\Header\AccessControlAllowCredentialsField
  • [ALE-635] Neue Klasse \Alvine\Net\Http\Header\Allow

Behoben

  • [ALE-636] Beim SMTP-Client funktioniert die Anmeldung nicht, wenn authorisation gesetzt wurde aber kein Username und kein Passwort.

1.18.0 - 2019-04-04

Hinzugefügt

  • [ALE-630] neue Funktion Alvine\Persistence\Relation\SQL\Functions\GroupConcat

Geändert

  • [ALE-631] Zusätzliche Statemens (ausserhalb von Field-Values für ein DefinedStatement zulassen
  • [ALE-632] Nicht gesetzte Standardvariablen wie SERVER_NAME muss abgefangen werden

1.17.0 - 2019-03-14

Hinzugefügt

  • [ALE-625] Neue Klasse Component

Geändert

  • [ALE-624] \Alvine\Persistence\ObjectNotFoundException sollte kein Logging schreiben
  • [ALE-626] Die Methode Logging\Handler\File::getInstance gibt File und nicht static zurück

Behoben

  • [ALE-627] Alvine\Persistence\Relation\SQL\Select\DefinedStatement

1.16.0 - 2019-02-08

Geändert

  • [ALE-622] Übergabe des PluralKeys über den Formatstring

1.15.0 - 2019-01-07

Geändert

  • [ALE-619] Erweiterung des Headers um x-helo-human

Behoben

  • [ALE-620] ReflectionClassConstant schmeisst Fehlermeldung das Parameter nicht verfügbar

1.14.0 - 2018-12-18

Geändert

  • [ALE-611] Permissionfehler gesondert abfangen
  • [ALE-613] Event-Subscriber muss immer eigene Instanz zurückgeben
  • [ALE-615] Die Überprüfung der Schreiberlaubnis braucht das Ergebnis des aktuellen Datensatzes

Behoben

  • [ALE-612] Überprüfung Testfunktion der Assembly Klasse
  • [ALE-614] Ändern des Benutzers funktioniert nicht
  • [ALE-617] Die Methode getObjectByID setzt Voraus das im Modell die Konstante OBJECT_ID definiert ist

1.13.0 - 2018-10-23

Hinzugefügt

  • [ALE-604] Hilfsfunktion OrOperator|AndOperator::getInstanceForValuesIn für erstellen von SQL-Abfragen
  • [ALE-605] Neue IsNull SQL-Funktions-Klasse

Geändert

  • [ALE-603] Timeout bei HTTP-Clients übergeben
  • [ALE-606] Die Alvine-Methode kann nun mit unset und isset auf properties umgehen
  • [ALE-609] Erweiterung der Sicherungsklassen und des Security-Context

Behoben

  • [ALE-581] Alvine\Persistence\Manager : findObject UUID durfte nicht 0 sein
  • [ALE-607] Im Mailclient darf kein Leerzeichen zwischen Befehl und Adresse stehen. Außerdem muss die E-Mail-Adresse in < und > stehen (siehe RFC)
  • [ALE-608] Wenn jemand die Konstante mimetypes überschreibt stimmen die Werte nicht mehr

1.12.0 - 2018-08-28

Hinzugefügt

  • [ALE-600] Neue DayOfWeek-Klasse und Berechnung von Referenzen

Geändert

  • [ALE-590] Umstellung ObjectType von stdClass auf Map
  • [ALE-591] Erweiterung der Standard-Logger-Namen
  • [ALE-594] Optimierungen für PHPUnit 7
  • [ALE-595] Verzeichnis bei Fehler Directory::create mit ausgeben
  • [ALE-597] Im Storeage wurde ein sonstiger Feheler (Badproperty) in einem NotFound Exception vesteckt.

Behoben

  • [ALE-592] Im Test erfolgt Prüfung noch auf StdClass und nicht Alvine\Core\Alvine.
  • [ALE-596] Es wird immer nur ein Eintrag hinzugeügt, da das Ergebnis von attach immer $this und damit true ist
  • [ALE-598] containProperty gibt es nicht

1.11.0 - 2018-06-08

Hinzugefügt

[ALE-586] Neue Tag und Tags Klasse [ALE-587] Neuer Header AccessControlAllowOriginField

Geändert

  • [ALE-578] Besseres Konfigurationsmanagement
  • [ALE-579] Konfiguration soll mit Streams umgehen können
  • [ALE-584] Komponenteneigenschaften nur laden, wenn es diese gibt
  • [ALE-588] Objekte können bei der Implementierung von toJson auf unterster Ebene auch Objekte sein und keine Arrays
  • [ALE-589] Umstellung des bindings von foreach über die Felder auf foreach über die records

Behoben

  • [ALE-580] PHP 7.2 Anpassungen
  • [ALE-583] mcrypt ist in PHP 7.2 deprecated

1.10.0 - 2018-02-20

Geändert

  • [ALE-556] Feldwerte müssen mit null umgehen können
  • [ALE-574] Client -> aufruf von context und connect absichern

1.9.0 - 2018-01-15

Hinzugefügt

  • [ALE-563] Neue Methode Session::clearTranscripts()
  • [ALE-564] Erweiterung der SQL-Klassen um Funktionen und Operatoren

Geändert

  • [ALE-562] Wird kein Wert im Datum übergeben sollte NULL zurückgegeben werden.
  • [ALE-567] Log automatisch flushen
  • [ALE-569] DefinedStatement::join um $append erweitert
  • [ALE-571] Die __toString-Methode sollte bei relationalen Feldern das Feld als SQL-Zeichenkette zurückgeben
  • [ALE-572] Statements müssen mit Records ausführbar sein, damit man kein SQL-Injection bekommt.

Behoben

  • [ALE-566] Die Datei GroupBy wird nicht gefunden
  • [ALE-568] Statusmeldung in HTTP\statuscode wird nicht verwendent
  • [ALE-570] HierarchicalProperties wirft bei bestimmten strukturen den Fehler Notice: Indirect modification of overloaded element

1.8.0 - 2017-10-31

Geändert

  • [ALE-561] Die AccessControlListImplementation verwendet nun kein associated mehr

Behoben

  • [ALE-559] Orderby nur ausführen, wenn der Wert gesetzt ist
  • [ALE-558] Map::getValue nicht im InstanceHelper aufrufen

1.7.0 - 2017-10-24

Hinzugefügt

  • Neues Interface Comparable
  • Neue Klasse FloatType für Floats
  • Neue Klasse HashMap für Objekte
  • Erweiterung der Locale-Klasse umd die Methode getLocaleString()
  • PropertyText mit addTemplate erweitern, damit Templates hinzugefügt werden können.
  • Übernahme der Modellierung der Persitence aus dem Application-Framework
  • Neue zentrale Modell-Klasse für die Datenhaltung
  • Neue Klasse \Alvine\Security\Authentication\Rightless

Geändert

  • Überarbeiten des Einheitensystems
  • Erweiterung um Statusprüfung in der Funktion dispose
  • Erweiterung der Persitese, damit auch Relationale-MYSQL Tabellen verwendet werden können
  • InvalidConfigurationException durch ConfigurationException ersetzen
  • SMTP-Klasse im Zuge von AWS SMS verbessern
  • Mindestanforderung für das Framework ist jetzt 7.1
  • ObjectStorage muss besser prüfen ob eine Manager verfügbar ist
  • Datumsklasse soll auch mit dem Datum 0000-00-00 umgehen können
  • Exceptions in ObjectStorage::getObject() nicht mehr verschleiern
  • Anpassungen in den SQL-Klassen

Behoben

  • Das Ergebnis von Component::getDependencyExceptions() ist ein Array
  • Implementierungen des Iterator-Interfaces mit einem false als Value laufen nicht durch
  • PreparedStatement werden nicht ausgeführt wenn der Type Boolean, aber der Wert eine Zeichenkette ist.
  • Korrektur der Extension Zuweisung in der sendEHLO Methode

1.6.0 - 2017-05-30

Hinzugefügt

  • Die Mailklasse kann nun auch über STARTTLS verwendet werden
  • Klassen haben nun einen Namespace in phpunit

Behoben

  • \Alvine\Date\Time::fromNow()
  • Zeichensatz korrigiert
  • Void ist in PHP 7.1 geschützt, daher Klasse geändert in VoidView

Ältere Veröffentlichungen

Hinzugefügt

  • [ALE-371] Neue Klasse Authentication
  • [ALE-373] Erweiterung der Route-Klasse um Zugriffsberechtigung
  • [ALE-381] Im Router die Zugangsprüfung einfügen
  • [ALE-383] Parser für XML Route erweitert
  • [ALE-385] Neue Klasse Alvine\Application\Web\Route\Permission
  • [ALE-387] DefaultAccessControl::containAuthenticationEntity()
  • [ALE-394] Nodelist erweitern um die Methode find
  • [ALE-398] Berechtigungsprüfung über die Gruppe der Autorität
  • [ALE-402] Berechtigungen über Gruppe
  • [ALE-404] Neuer statuscode
  • [ALE-415] Neue Methode Form::disableBrowserValidate() um dieses Attribute einzufügen
  • [ALE-418] Berechtigungen bei der Verwendung von ObjectStorage auf Record-Ebene
  • [ALE-434] Neue Klasse für ContentDispositionField
  • [ALE-436] Neue Image-Klasse für HTML-UI
  • [ALE-437] XML-Node um getAttributes erweitern um auf die Attribute zugreifen zu können
  • [ALE-438] Implementierung des \Alvine\Core\InstantiableFromProperties Interfaces
  • [ALE-441] Neue Methode Directory::isAbsolute() und File:::isAbsolute()
  • [ALE-447] Neue Header-Felder integriert Server, Via, TE, Trailer
  • [ALE-448] XML-Tags sind meist kleingeschrieben und die Tags-Klassen sind in CamelCase, das führt dazu das diese nicht gefunden werden.
  • [ALE-450] Neues Interface Instantiable
  • [ALE-455] Neue Managerklasse für den Zugriff auf die Storage-Objekte
  • [ALE-456] Gebundene Objekte innerhalb von Objekten einzeln abspeichern
  • [ALE-459] Über den Manager gespeicherte Objekte sollen beim laden die gleiche Referenz erhalten

Verbessert

  • [ALE-356] Der Name String ist in PHP7 reserviert
  • [ALE-361] Socketklasse: Das Timeout gilt nicht, wenn Zeichen übertragen werden.
  • [ALE-362] Das Schreiben der Logger-Handler im Finalize der Anwendung gezielt anstoßen
  • [ALE-368] PHP 7 Umstellung für Parameter\String
  • [ALE-375] Feintuning des Anmeldesystems
  • [ALE-376] Neue Klasse ClassType
  • [ALE-377] Aufräumarbeiten und Dokumentation
  • [ALE-379] IndexObserver üerarbeiten, dass dieser über ein Mapping dynamisch erweitert werden kann
  • [ALE-380] Alvine Basisklasse um isInstanceOf ergänzt und getClass() auf ClassType umstellen
  • [ALE-382] Session und Web::authenticate() optimieren
  • [ALE-384] Signatur des \Alvine\Application\Web\View\Intern\Error::getInstance() den Presenter übergeben
  • [ALE-386] Erweiterung der Identity-Klassen zum Anlegen von Objekten mit definierter ID
  • [ALE-390] Erweiterung, damit das File-Objekt auch mit anderen Resourcen umgehen kann
  • [ALE-392] ClassType ergänzen, damit man Namespace und Klassenname ermitteln kann
  • [ALE-396] Umstellung auf Bootstrap 4
  • [ALE-400] Diverse Testfälle nachziehen um Codeabdeckung zu erhöhen
  • [ALE-406] Beim Anlegen eines Queries Übernahme der Sortierung aus dem FilterDokument
  • [ALE-408] Anpassungen im Rahmen der Anwenungsoptimierung
  • [ALE-409] Copyrightumstellung auf 2016
  • [ALE-410] Beim Parsen einer XML-Node sollen bei closes Nodes den Wert null und nicht =‘’ erhalten
  • [ALE-411] Environment auch für cygwin Umgebung richtigen Wert für OS zurückgeben lassen
  • [ALE-412] Fehlermeldung in den UI-Controls an Bootstrap 4 angepasst
  • [ALE-413] ID des Attributes im Delete-Button auch als ID des Objekts speichern und type=submit für den button setzen
  • [ALE-414] Logging im HTTP-Client auf Trace umstellen
  • [ALE-416] Löschbutton um die Klasse alvineFormAjax ergänzt um diesen per Jquery abzusenden
  • [ALE-417] Beim Löschen von temporären Dateien im destruktor funktioniert die Exception nicht
  • [ALE-420] Float ist in PHP7 reserviert
  • [ALE-423] Die Nutzung von uasort ist teilweise unsauber implementiert.
  • [ALE-429] Performanceoptimierung der Type-Klasse
  • [ALE-432] Implementierung des SeekableInterfaces in der NodeList
  • [ALE-433] Die Methoden dürfen den internen Counter nicht verändern
  • [ALE-442] Wenn kein Verzeichnis angegeben wurde soll kein mkdir aufgerufen werden, sondern direkt eine Exception geworfen werden.
  • [ALE-451] Im Producer die Config in der Exception ausgeben, damit geprüft werden kann, ob diese verfügbar
  • [ALE-454] Indexierung der AccessControllListe
  • [ALE-461] Erstellen einer File-URL aus einem Directory vereinfachen

Behoben

  • [ALE-321] Filter in den Objekt-Views zeigt keine Werte an
  • [ALE-363] Löschen der Sortierung in der Liste geht nicht
  • [ALE-366] Leere Konfigurationswerte verursachen einen Fehler beim lesen des nächsten Wertes
  • [ALE-367] ENV Variablen werden nicht ersetzt
  • [ALE-378] Vom IndexFinder muss es mehrere Objekte geben können
  • [ALE-388] Die Property-Klasse schliesst einen Stream nicht sorgfältig
  • [ALE-389] php://memory wird nicht richtig dargestellt
  • [ALE-391] Loghandler File-Handler umstellen, das beim Typ Stream kein Verzeichnis erstellt wird
  • [ALE-399] Im Riak-Test wird auf ein nicht vorhandendes Bild getestet
  • [ALE-401] Der Test Alvine\I18n\Util\PluralRulesTest::testParsingException3 schlägt fehl
  • [ALE-403] Wenn das Trennzeichen am Ende steht “Entscheidung:” wird eine Exception geworfen
  • [ALE-405] In der ObjectStorage::writeObject() wurden Exceptions abgefangen ohne diese weiter zu bearbeiten
  • [ALE-407] Resourcepath ist ein String und kein Objekt in der View-Klasse
  • [ALE-421] Die Sortierreihenfolge im Accepted-Header stimmt bei PHP7 nicht mehr
  • [ALE-425] Bei einem Redirect wird der “alte” Body mitgesendet
  • [ALE-426] Proxyhandling auch im Redirect
  • [ALE-427] Die Namespaces von MySQL und SQLite sind besonders und müssen explizit kodiert werden.
  • [ALE-428] HTTP-Fehler beim Aufruf von Solr
  • [ALE-430] Umstellen des Parsings der ValueMap auf PHP-Funktion parseString
  • [ALE-431] Der Aufruf von NodeList::getChildren() führt zu einem Fehler wenn es keine Nodes gibt.
  • [ALE-435] Der Header im REST-Client kann null sein
  • [ALE-440] File::getDirecotry() gibt das Protokoll nicht zurück, das führt bei phar zu Problemen
  • [ALE-458] Alvine\Persistence\ManagerTest Failed
  • [ALE-460] Alvine\Net\Http\Header\ContentTypeField bei text\xml muss per default utf-8 zurück geliefert werden

Glossar

Docker

Mit Hilfe von Docker kann man Anwendungen mithilfe von Betriebssystemvirtualisierung in Containern isolieren. Docker, Wikipedia

PHP

PHP ist eine Skriptsprache, die im Design einige schwächen aufzeigt und viele Aspekte nicht sauber implementiert (z.B. Reihenfolge der Argumente bei Funktionen), jedoch durch einen großen Pool von Dokumentationen, Beispielen, Tutorials den Einstieg leicht macht.

Javascript

Javascript ist gut dokumentiert und wird von allen Browsern unterstützt.

HTML / CSS

HTML - in der Version 5 - enthält alle notwendigen Strukturen um Anwendungen und Webseiten gut, schnell zu entwickeln. In Zusammenspiel mit CSS können Inhalt, Struktur und Aussehen gut getrennt verwaltet und entwickelt werden.

XML

XML hat viele Vorteile die - besonders im Bereich Datenaustausch - die eine bessere Implementierung versprechen und die Fehlersuche erleichtern. Allerdings steht dem entgegen ein deutlich größerer Ressourcenverbrauch bei der Verarbeitung der Daten. In Alvine2 wird daher besonders im Bereich Datenaustausch auf XML gesetzt, allerdings nicht für die interne Verarbeitung.

Troubleshooting

Allgemeines

Wie kann ich die Ausgabe von print_r oder var_dump formatieren?

Die Funktionen print_r und var_dump geben die Ausgabe direkt aus. Um die Ausgabe zu formatieren, kann die Funktion ob_start verwendet werden.

ob_start();
print_r($var);
$var = ob_get_clean();

Imprint

Responsible for the content

schukai GmbH
Eichenstraße 26
82290 Landsberied
Germany

[email protected]
+49-8141-5098888
schukai.com