Eigene Blade-Direktiven schreiben – aber richtig

Ich experimentiere gerade mit einem Laravel-Projekt und bin dabei auf ein Problem gestoßen, das mich eine ganze Weile hat verzweifeln lassen. Am Ende macht es natürlich total Sinn, dass das so passiert, aber wenn man mit dem Framework gerade anfängt, sieht man das nicht so. Falls ihr per Google auf diesen Beitrag gestoßen seid: Ich arbeite mit Laravel 5.8. Monate und Jahre später wird es sicher wieder andere Wege geben, um das Geschilderte zu erreichen. Wie man eine Blade-Direktive aufbaut, damit sie mit dem View-Cache harmoniert, bleibt aber vermutlich erst mal so.

Was habe ich also angestellt? Ich habe ein Formular gebaut und fand es hässlich, dort immer wieder den gleichen Code ins HTML schreiben zu müssen, damit das Label eines Formularfeldes ausgegeben wird oder die dazugehörigen Validierungsfehler. Also habe ich mir dafür eigene Blade-Direktiven geschrieben und so z.B. das hier…

@error('name')
    <span class="invalid-feedback" role="alert">{{ $errors->first('name') }}</span>
@enderror

…durch das hier ersetzt:

@form_error('name')

Das erreicht man, indem man für die Template-Engine „Blade“ eine eigene Direktive schreibt, z.B. in der „boot“-Methode des AppServiceProvider:

// add a new label Blade directive: @form_error(id)
Blade::directive('form_error', function ($expression) {
	// remove the quotation marks, which are passed in here
	$expression = str_replace(['\'', '"'], '', $expression);
	
	// check if there are any errors for this field
	$errors = session()->get('errors') ?: new ViewErrorBag();
	$message = $errors->first($expression);
	
	// If there are, then output the appropriate code.
	if ($message) {
		$message = e($message);
		return "<?php echo '<span class=\"invalid-feedback\" role=\"alert\">{$message}</span>'; ?>";
	}
	return '';
});

Soweit so schön. Man kann nun das Projekt normal speichern und alles sieht gut aus. Wenn man das Feld leer lässt, wird das angemeckert. Wenn man den Namen eines anderen, existierenden Projekts einträgt, wird das auch angemeckert – aber mit der gleichen Meldung wie eben, die ja nun gar nicht zum eigentlichen Fehler passt.

Validierungsfehler

Anfangs wurde ich hier draus nicht wirklich schlau. Mit einigem Rumprobieren habe ich mich schließlich zu der Erkenntnis vorgetastet, dass die korrekte Meldung angezeigt wird, wenn man mit „php artisan view:clear“ den View-Cache leert. Aber dann wird beharrlich diese Meldung angezeigt, bis man eben wieder den Cache leert. Die Lösung liegt an der Art, wie Blade diese selbstgeschriebenen Direktiven behandelt. Es ist offenbar nicht vorgesehen, dass man hier Logik einbaut, was ich natürlich prompt gemacht habe. Vielmehr soll die Logik Teil des PHP-Strings sein, welchen die Direktive zurückgibt. Der kann dann wiederum gecacht werden. Es versteht sich von selbst, dass man auf diese Weise keine aufwändigeren Funktionen umsetzen kann und sollte, aber um immer wiederkehrende kleine Schnipsel wiederverwendbar abzulegen, mag das OK sein.

In meinem Fall darf ich also nicht eine bestimmte Message innerhalb der Direktive ermitteln und dann fest in die Ausgabe packen, sonst wird diese Message gecacht. Vielmehr muss es so aussehen:

// add a new label Blade directive: @form_error(id)
Blade::directive('form_error', function ($expression) {
	return <<<PHP
		<?php 
		if (\$errors->has($expression)) {
			echo '<span class="invalid-feedback" role="alert">' . e(\$errors->first($expression)) . '</span>';
		}
		?>
PHP;
});

Wieso manche der Dollars escaped sind und andere nicht, erschließt sich, wenn man unter „storage/framework/views“ mal schaut, was Blade daraus macht:

<div class="form-group">
	<label for="name" class="required">Project Name:</label>
	<input type="text" class="<?php echo e(getFormElementStyles('name')); ?>" name="name" id="name" value="<?php echo e(old('name', $project->name)); ?>"/>
					<?php 
	if ($errors->has('name')) {
		echo '<span class="invalid-feedback" role="alert">' . e($errors->first('name')) . '</span>';
	}
	?>
</div>

Man sieht: $expression wurde im Ergebnis durch den Wert ersetzt, inclusive der mit hereingereichten Anführungszeichen. $errors dagegen muss als Variable bis ins eigentliche Template gelangen. Diese zwei Ebenen sind am Anfang übelst verwirrend. Da muss man sich wirklich erst mal reindenken.

Ach ja, wenn man am Code etwas geändert hat, muss man tatsächlich einmal den View-Cache leeren. Danach klappt es aber nun ohne Rumfummeln am Cache mit der Anzeige der jeweils passenden Fehlermeldung.

So richtig super zufrieden bin ich mit der Lösung übrigens noch nicht. Aber wenigstens kann ich jetzt die Ausgabe von Validierungsfehlern zentral steuern, ohne im Template zu viel Code stehen zu haben. Falls ihr eine bessere Lösung für die Abstraktion der Formular-Darstellung kennt, freue ich mich über Kommentare.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Bitte beachte die Kommentarregeln: 1) Kein Spam, und bitte höflich bleiben. 2) Ins Namensfeld gehört ein Name. Gerne ein Pseudonym, aber bitte keine Keywords. 3) Keine kommerziellen Links, außer es hat Bezug zum Beitrag. mehr Details...

So, noch mal kurz drüber schauen und dann nichts wie ab damit. Vielen Dank fürs Kommentieren! :-)