contao/core — pull request #4592 by tristanlins (2012-08-05 19:31:59+00:00)
Title: Feature RuntimeClassLoader

Das hier ist die Fortsetzung des letzten Pull Requests #4526 für echte Namespaces in Contao 3.

Zuerst einmal vorne weg, Leo hat ein paar Commits eingebaut, unter anderem den c496e0d61ff28dbc063ee1719f6472ebd97b9dde. Der Autoloader Registriert nun alle Klassen, egal in welcher Tiefe. Damit ist das DevTool grundsätzlich PSR-0 kompatibel, weil man seine Klassenhierarchie nach PSR-0 aufbauen kann. Das DevTool wird zwar zum registrieren weiterhin benötigt, ich hatte Leo aber bereits gesagt das ich persönlich damit kein Problem habe.
(Für die Entwicklung werde ich vermutlich einen PSR-0 ClassLoader in das ER stellen, ausliefern sollte jeder Entwickler seine Extension allerdings mit einer vollständigen autoload.php).

----

Des weiteren hat Leo den Commit dd4d17f529e4f06b2dba110c7b7260382f7a8162 eingeführt, womit nur noch der 1. Teil des Namespaces registriert wird.

Das größte Problem was wir haben ist die Fähigkeit, Klassen verlustfrei zu überschreiben. Mein Vorschlag aus dem Vorangegangenen Pull Request #4526 ist, den Namespace `Runtime\` als Präfix hinzu zu nehmen. Anstelle der Klasse `Avisota\Mail\Transport` würde man die Klasse `Runtime\Avisota\Mail\Transport` verwenden.
Nach Leos Änderung wäre es aber auch möglich anstelle von `Avisota\Mail\Transport` die Klasse `Mail\Transport` zu verwenden. Das ist quasi das gleiche Prinzip wie mit den Klassen im globalen Namespace. Hier gilt also, wenn eine andere Erweiterung die Klasse `Kunde\Mail\Transport` später definiert, wird diese als Implementierung für `Mail\Transport` verwendet.

2 gültige Lösungen:
- Runtime\ Präfix kommt hinzu
- 1. Namespace Teil fällt weg

Leos Lösung hat meiner Meinung nach allerdings überwiegende Nachteile, gegenüber meiner `Runtime\` Präfix Lösung.

----

Zuerst will ich die Performance der ClassLoaders betrachten, genauer die Prüfungen die durchgeführt werden müssen um eine Klasse zu finden.

Contao ClassLoader
```
1x prüfe ob Klasse in self::$classes existiert
(N+1)x prüfe ob NS+Klasse in self::$classes existiert // N=Anzahl der registrierten Namespaces +1 wegen dem Contao\ Namespace
= (N+2) prüfe Existenz in self::$classes
```

Gehen wir mal davon aus, dass jemand 20 Extensions installiert hat (bei mir ist das zumindest eine übliche Menge, meistens habe ich mehr).

```
= 22x prüfe Existenz in self::$classes für jede Klasse. // Worst-Case
```

Um das ganze nach zu vollziehen muss man sich mit der Arbeitsweise des ClassLoader's auseinander setzen.
https://github.com/contao/core/blob/develop/system/library/Contao/ClassLoader.php#L198
Der Faktor N ensteht durch eine foreach Schleife, mit der alle registrierten Namespaces durchlaufen werden:
https://github.com/contao/core/blob/develop/system/library/Contao/ClassLoader.php#L234

----

Meine Lösung mit dem `Runtime\` Präfix und einem Mapping zum überschreiben von Klassen, hat deutlich weniger überprüfungen.

```
1x prüfe ob Klasse in RuntimeClassLoader::$classMapping existiert
1x prüfe ob Klasse in ClassLoader::$classes existiert
```

Zum prüfen ob die Originalklasse existiert, verwende ich einfach den vorhandenen ClassLoader Stack, dadurch bleibt der `RuntimeClassLoader` schlank und bedient sich bereits vorhandener Logik.

Bei dieser Lösung gäbe es allerdings maximal 2 Prüfungen um die Klasse zu finden (abgesehen vom Fall-Back wo dann der ganze Stack durchlaufen wird), egal wie viele Extensions installiert sind.
Meine Lösung skaliert also besser.

----

Ein weiteres Problem von Leos Lösung sind Kollisionen.
Ein akutes Beispiel ist das Mailer Framework, dort gibt es auch eine Transport-Klasse.
Dann gäbe es plötzlich `Avisota\Mail\Transport`
und es gäbe `Mailer\Mail\Transport`
Beiden würden sich überschreiben obwohl sie nichts mit einander zu tun haben.
Dies ließe sich nur verhindern, in dem man einen Doppel-Vendor (oder wie man das auch immer nennen mag) verwendet.
Die Klassen müssten dann `InfinitySoft\Avisota\Mail\Transport` und `InfinitySoft\Mailer\Mail\Transport` heißen und verwendet werden würden `Avisota\Mail\Transport` und `Mailer\Mail\Transport`.

----

Dem Grunde nach verfolgen beide Lösungen eigentlich das gleiche, Leo nimmst den 1. Namespace-Part weg, ich dichte `Runtime\` hinzu. **Beide** Lösungen sind gewöhnungsbedürftig und nicht Standard-konform.

Alles in allem würde ich allerdings persönlich doch eher zu der `Runtime\` Lösung tendieren, aus folgenden Gründen:

- es geht nichts verloren (bei Leos Lösung geht ein Teil vom Namespace verloren, was zu Verwirrung führen kann)
- die Prüfung im Class Loader skaliert besser.
- die `Runtime\` Variante ist nicht Kollisionsgefährdet (wie ich bereits oben beschrieben habe, besteht durch den Wegfall eines Teils des NS ein Konfliktpotential)

Ansonsten haben beide Lösungen die gleichen Probleme:
- der Entwickler kann das ganze umgehen und direkt `Avisota\Mail\Transport` anstelle von `Mail\Transport` bzw. `Runtime\Avisota\Mail\Transport` verwenden. (hier ließe sich in beiden Fällen eine Warning in den Class Loader einbauen)
- überschreiben lässt sich nur durch automatisches (Leos Variante) oder manuelles (meine Variante) Mapping realisieren

Alles in allem ist immer der Entwickler gefragt, er muss sich an den Core halten oder eben nicht.

----

Für das manuelle Mapping kommt in meinem Fall eine `config/config.ini` zum Einsatz. Dazu baue ich auf den Pull Request #4591 auf, mit dem man als Entwickler das DevTool konfigurieren kann. Z.B. kann man das Registrieren der Namespaces dadurch unterbinden. Diese Idee hatte Leo bei unserem Tel-Gspr. am Freitag, ich habe es jetzt nur mal umgesetzt.

Ich erweitere diese Funktionalität um das Klassen-Mapping, wie das aussieht kann man in meiner Demo Extension anschauen. https://github.com/InfinitySoft/contao3-poc-namespaces-extension/blob/master/system/modules/NamespacesExtension/config/config.ini