Composer für PHP nutzen

Composer ist ein Paket- bzw. Dependency-Manager für PHP. Damit ist es möglich die Abhängigkeiten von PHP-Projekten komfortabel zu verwalten und aktuell zu halten. Dazu wird in dem Projekt eine Datei mit dem Namen composer.json angelegt. In dieser Datei finden sich die Abhängigkeiten für das Projekt:

{
  "minimum-stability": "RC",
  "require": {
    "slim/slim": "3.*"
  }
}

Um diese Abhängigkeiten initial dem Projekt hinzuzufügen, muss der Befehl:

composer install

eingegeben werden. Anschließend werden die Abhängigkeiten aufgelöst und heruntergeladen:

Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 6 installs, 0 updates, 0 removals
  - Installing psr/container (1.0.0): Loading from cache
  - Installing container-interop/container-interop (1.2.0): Loading from cache
  - Installing nikic/fast-route (v1.3.0): Loading from cache
  - Installing psr/http-message (1.0.1): Loading from cache
  - Installing pimple/pimple (v3.2.3): Loading from cache
  - Installing slim/slim (3.12.1): Loading from cache
Writing lock file
Generating autoload files

Nach der initialen Installation, finden die sich die Abhängigkeiten im Ordner vendor. Daneben wird eine composer.lock-Datei angelegt. In dieser sind die genauen Versionsnummern, der installierten Abhängigkeiten hinterlegt. Würde das Projekt nun nochmal mit der install-Option aufgerufen werden, so würden exakt diese Versionen installiert. Sollen diese Abhängigkeiten später aktualisiert werden, kann dafür der Befehl:

composer update

genutzt werden. Dabei werden die Versionen, welche sich in der composer-lock-Datei befinden ebenfalls aktualisiert.

getcomposer.org

Bezogen werden kann Composer unter anderem auf der offiziellen Seite unter getcomposer.org. Der Quelltext von Composer ist auf GitHub zu finden. Die Software ist unter der MIT-Lizenz lizenziert und damit freie Software.

Geocoding in Verbindung mit einer Leaflet-App

Gestern schrieb ich in einem Artikel, wie Adressen aus Koordinaten mittels des Dienstes Nominatim, unter Zuhilfenahme von OpenStreetMap-Daten, ermittelt werden können. Mithilfe der freien Bibliothek Leafleat, kann dies nun in eine kleine Webapplikation gegossen werden.

Anhand der Geo-Koordinate wird die Adresse ermittelt und dargestellt.

Ziel ist es eine Applikation zu bauen, welche eine Karte darstellt und bei einem Klick auf diese Karte den Geocoding-Dienst anfragt. Sobald die Antwort vom Dienst eingetroffen ist, soll in einem Pop-up die Adresse dargestellt werden. Nachdem ein Grundgerüst in Form einer HTML-Datei erstellt wurde, werden dort die benötigten Bibliotheken (Leaflet und jQuery) eingebunden. Anschließend kann die Karte erzeugt werden. Damit diese im Vollbild dargestellt wird, findet sich eine entsprechende CSS-Definition in der Datei:

<style type="text/css">
	body {
		padding: 0;
		margin: 0;
	}

	html, body, #map {
		height: 100%;
	}
</style>

Anschließend kann der TileLayer angelegt werden und der Map zugewiesen werden:

// Options
var centerLatitude=53.49577;
var centerLongitude=13.30873;
var zoomLevel=15;

// Define layer
var openStreetMapLayer = new L.TileLayer(
		'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
		{attribution: '<a href="https://www.openstreetmap.org/copyright">© OpenStreetMap contributors</a>'});

// Define map
var map = L.map('map', {
	center: new L.LatLng(centerLatitude, centerLongitude),
	zoom: zoomLevel,
	layers: [openStreetMapLayer]
});

Für die Karte muss im nächsten Schritt ein Click-Event definiert werden, welches den AJAX-Request gegen den Geocoding-Dienst auslöst und den Marker erstellt. Der jeweilige Marker wird in eine separate Ebene gepackt und beim nächsten Klick wieder gelöscht und anschließend ein neuer Marker erstellt:

// Marker managment
var markerLayer;
map.on('click', function(e){

	// Console debug
	var coord = e.latlng;
	var lat = coord.lat;
	var lng = coord.lng;
	console.log("You clicked the map at latitude: " + lat + " and longitude: " + lng);

	// Marker managment		
	if(markerLayer != undefined) {
		map.removeLayer(markerLayer);
	}
	
	markerLayer = new L.Marker(e.latlng);
	map.addLayer(markerLayer);

	// Call geocode api
	var request =	$.ajax({
		method: "GET",
		url: "https://nominatim.openstreetmap.org/reverse?lat=" + lat + "&lon=" + lng + "&format=json"
	});

	request.done(function( json ) {
		console.log(json);
		markerLayer.bindPopup(json.display_name).openPopup();
	});
});

Am Stück sieht die Applikation wie folgt aus:

<!DOCTYPE html>
<html>
<head>
	<title>Leaflet address encoder</title>
	<meta charset="utf-8" />

	<meta name="viewport" content="width=device-width, initial-scale=1.0">

	<link rel="stylesheet" href="https://unpkg.com/leaflet@1.5.1/dist/leaflet.css"
   integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
   crossorigin=""/>
	
	<style type="text/css">
		body {
			padding: 0;
			margin: 0;
		}

		html, body, #map {
			height: 100%;
		}
	</style>
	
	<script src="https://unpkg.com/leaflet@1.5.1/dist/leaflet.js"
   integrity="sha512-GffPMF3RvMeYyc1LWMHtK8EbPv0iNZ8/oTtHPx9/cc2ILxQ+u905qIwdpULaqDkyBKgOaB57QTMg7ztg8Jm2Og=="
   crossorigin=""></script>
	<script type="text/javascript" src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
</head>
<body>
	<div id="map"></div>
	
	<script>
	        // Options
		var centerLatitude=53.49577;
		var centerLongitude=13.30873;
		var zoomLevel=15;
		
		// Define layer
		var openStreetMapLayer = new L.TileLayer(
				'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
				{attribution: '<a href="https://www.openstreetmap.org/copyright">© OpenStreetMap contributors</a>'});
		
		// Define map
		var map = L.map('map', {
		    center: new L.LatLng(centerLatitude, centerLongitude),
		    zoom: zoomLevel,
		    layers: [openStreetMapLayer]
		});
		
		// Marker managment
		var markerLayer;
		map.on('click', function(e){

			// Console debug
			var coord = e.latlng;
			var lat = coord.lat;
			var lng = coord.lng;
			console.log("You clicked the map at latitude: " + lat + " and longitude: " + lng);

			// Marker managment		
			if(markerLayer != undefined) {
				map.removeLayer(markerLayer);
			}
			
			markerLayer = new L.Marker(e.latlng);
       		map.addLayer(markerLayer);

			// Call geocode api
			var request =	$.ajax({
				method: "GET",
				url: "https://nominatim.openstreetmap.org/reverse?lat=" + lat + "&lon=" + lng + "&format=json"
			});

			request.done(function( json ) {
				console.log(json);
				markerLayer.bindPopup(json.display_name).openPopup();
			});
  		});
	</script>
</body>
</html>

Alternativ kann der Quelltext heruntergeladen bzw. ausprobiert werden.

Daten unter Java in ein Byte-Array umwandeln

Für ein Projekt wollte ich eine Liste von long-Werten unter Java in ein einzelnes Byte-Array umwandeln. Dafür kann unter Java die Klasse ByteBuffer genutzt werden. In diesem Beispiel wird eine Liste namens dataStream definiert:

List dataStream = new ArrayList<>();

In dieser Liste befinden sich eine unbestimmte Anzahl von Werten. Mit der Klasse ByteBuffer kann diese Liste bzw. die Werte in der Liste in ein Byte-Array überführt werden:

ByteBuffer byteBuffer = ByteBuffer.allocate(dataStream.size() * 8);

for(long value: dataStream) {
	byteBuffer.putLong(value);
}

byte[] array = byteBuffer.array();

Im ersten Schritt wird der ByteBuffer mit einer Kapazität angelegt, welcher der Größe der long-Werte in Byte entspricht. Anschließend werden die long-Werte in den ByteBuffer geschrieben. Ist dies geschehen kann ein Byte-Array aus dem ByteBuffer erstellt werden. Damit ist die Umwandlung abgeschlossen.

The Twelve-Factor App

Softwareentwicklung ist ein komplexes Pflaster. Da ist es praktisch, wenn der geneigte Entwickler eine Methodologie an die Hand bekommt, welche das Ergebnis der Entwicklung verbessert. Eine solche Methodologie ist die The Twelve-Factor App. Diese ursprünglich 2011 vorgestellte Methodologie kommt mit besagten zwölf Punkten aus. Diese führen von der Codebasis über die Abhängigkeiten bis hin zum Deployment. Zielgruppe der Methodologie sind Applikationen, welche als Service laufen.

12factor.net

Neben dem englischen Original existieren eine Reihe von Übersetzungen; unter anderem in die deutsche Sprache. Gelesen werden kann die Dokumentation der Methodologie unter 12factor.net. Lizenziert ist die Webseite unter der MIT-Lizenz und damit freie Software. Der Quelltext ist auf GitHub zu finden.

Benchmarking mit dem JMH-Framework

Benchmarking unter Java hat mit einigen Problemen zu kämpfen. So optimiert die Java-Laufzeitumgebung den Quellcode, je nachdem wie oft er benutzt wird. Wenn nun kleinere Dinge getestet werden sollen, wie z.B. ob Methode A oder B besser funktioniert so wird dies problematisch. Am Anfang würde besagte Methoden nur interpretiert werden, anschließend würden sie bei häufiger Benutzung kompiliert werden und bei noch häufigerer Benutzung weiter optimiert werden. Daneben kann es passieren dass der Benchmark komplett wegoptimiert wird, da die JVM unter Umständen erkennt, dass die genutzten Werte bzw. die Ergebnisse des Benchmarks später nicht mehr genutzt werden.

Die offizielle Seite des Java Microbenchmark Harness-Framework

Damit sich ein Entwickler nicht immer und immer wieder mit solchen Problemen beim Benchmarking herumschlagen muss, wird seit einigen Jahren im Rahmen des OpenJDK-Projektes an dem Java Microbenchmark Harness-Framework gearbeitet. Das JMH-Framework nimmt dem Entwickler viele Aufgaben ab, um diese Probleme zu lösen. So kann bzw. wird die JVM durch JMH aufgewärmt werden und auch die Messung der Zeiten übernimmt JMH. Um JMH zu nutzen, müssen die entsprechenden Abhängigkeiten (in diesem Fall über die Maven-POM-Datei) dem Projekt hinzugefügt werden:

<dependency>
	<groupId>org.openjdk.jmh</groupId>
	<artifactId>jmh-core</artifactId>
	<version>1.21</version>
</dependency>
<dependency>
	<groupId>org.openjdk.jmh</groupId>
	<artifactId>jmh-generator-annprocess</artifactId>
	<version>1.21</version>
</dependency>

Sind die Abhängigkeiten eingebunden, kann mit dem eigentlichen Aufbau des Benchmarks begonnen werden. Jede Methode, welche ein Benchmark durchführen möchte, muss mit der @Benchmark-Annotation gekennzeichnet werden:

@Benchmark
public int addInts() {
    return aInt + bInt;
}

Die Klasse, in welcher die Benchmarks definiert sind, muss mit einigen weiteren Annotationen ausgestattet werden:

@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class AddingBenchmark {
...

Die @State(Scope.Benchmark)-Annotation in diesem Beispiel sorgt dafür das die Felder im Rahmen des Benchmarks für die Benchmark-Methoden zur Verfügung stehen. Mit der @BenchmarkMode-Annotation wird definiert, welche Art von Messung vorgenommen werden soll. In diesem Fall wird die durchschnittliche Zeit für eine einzelne Operation gemessen und ausgegeben. Über die @OutputTimeUnit-Annotation wird definiert wie die Zeiten für die Messungen ausgegeben werden. Werden für den Benchmark Daten benötigt so können sie über eine Methode, welche mit der @SetupAnnotation versehen wird, erzeugt werden:

@Setup
public void setup() {
    ...
}

Die setup-Methode wird damit vor dem eigentlichen Benchmark ausgeführt und somit können benötigte Daten dort erzeugt werden. Damit der Benchmark nun ausgeführt werden kann muss das Java Microbenchmark Harness-Framework angestartet werden. Dafür wird eine Klasse geschrieben und mit einer main-Methode versehen:

package net.seeseekey.JavaBenchmark;

import org.openjdk.jmh.runner.RunnerException;

import java.io.IOException;

public class Runner {

    public static void main(String[] args) throws IOException, RunnerException {
        
        org.openjdk.jmh.Main.main(args);
    }
}

Über die main-Methode wird die main-Methode des Frameworks gestartet und damit wird das Benchmark durchgeführt. Alternativ kann die Methode org.openjdk.jmh.Main.main auch direkt in der Maven-POM-Datei als Startklasse definiert werden:

<plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <version>3.1.0</version>
    <configuration>
        <archive>
            <manifest>
                <mainClass>org.openjdk.jmh.Main.main</mainClass>
            </manifest>

Nachdem der Benchmark implementiert ist, kann dieser natürlich über die IDE ausgeführt werden. Das würde allerdings nicht ganz der Intention von JMH entsprechen. Stattdessen sollte eine JAR aus dem Projekt erzeugt werden und diese, auf einem möglichst unbelastetem System, über die Konsole ausgeführt werden:

java -jar benchmark.jar

Damit wird der Benchmark über JMH ausgeführt. JMH beginnt mit einer Warmup-Phase und führt anschließend den eigentlichen Benchmark durch. Nach einigen Minuten erhält der Entwickler die Auswertung des Benchmark:

Benchmark                                 Mode  Cnt   Score    Error  Units
JavaBenchmark.AddingBenchmark.addDoubles  avgt   25  ≈ 10⁻⁵           ms/op
JavaBenchmark.AddingBenchmark.addInts     avgt   25  ≈ 10⁻⁵           ms/op

In diesem Fall sieht der Entwickler bei der Ausgabe, dass die Ausgabeeinheit für die Zeit mit Millisekunden zu grob gewählt wurde und stattdessen besser Nanosekunden genutzt werden sollten.