RFC – doplnit typovou kontrolu v TemplateFactory ?
- m.brecher
- Generous Backer | 905
Ahoj,
pokud někomu nevyhovuje vestavěná Nette\Bridges\ApplicationLatte\DefaultTemplate, má možnost si v konfiguraci nastavit vlastní:
latte:
templateClass: App\Template\MyTemplate
a ve vlastní šabloně si definovat předávané parametry podle vlastního uvážení:
#[AllowDynamicProperties]
class MyTemplate extends Nette\Bridges\ApplicationLatte\Template
{
public string $myParam;
}
vše funguje do doby, kdy se rozhodneme použít jméno proměnné exitující v built-in šabloně nette, třeba $user ale předat do ní jiný typ např. místo Nette\Security\User nějaké readonly UserDTO. Nette vyhodí TypeError. Výjimku vyhazuje Nette\Bridges\ApplicationLatte\TemplateFactory::createTemplate() zde:
$params = [
'user' => $this->user,
'baseUrl' => $baseUrl,
'basePath' => $baseUrl ? preg_replace('#https?://[^/]+#A', '', $baseUrl) : null,
'flashes' => $flashes,
'control' => $control,
'presenter' => $presenter,
];
foreach ($params as $key => $value) {
if ($value !== null && property_exists($template, $key)) {
$template->$key = $value;
}
}
TemplateFactory předává parametry, jako do DefaultTemplate, kontroluje existenci property a předpokládá stejný typ.
Jak toto omezení custom template (nemožnost definice typu jaký potřebuji) v TemplateFactory vyřešit ?
Ideální by bylo, pokud vývojář použije vlastní template class, nechat injektáž proměnných na něm, ale tato změna v kódu TemplateFactory by znamenala nepříjemný BC break.
Druhou možností je ponechat injekci parametrů jak je a přidat typovou kontrolu tak, aby se zbytečně nesnižoval výkon aplikace.
Zkoušel jsem toto řešení které jednoduchým způsobem problém řeší:
$checkTypes = !is_a($template, DefaultTemplate::class);
if($checkTypes){
$properties = [];
$rc = new ReflectionClass($template);
foreach($rc->getProperties(\ReflectionProperty::IS_PUBLIC) as $property){
$properties[$property->name] = $property;
}
foreach ($params as $key => $value) {
if ($value !== null && isset($properties[$key])) {
$property = $properties[$key];
$type = $property->getType();
if($type instanceof ReflectionNamedType){
$expected = $type->getName();
$isValid = $type->isBuiltin() ? match($expected){
'string' => is_string($value),
'int' => is_int($value),
'bool' => is_bool($value),
'array' => is_array($value),
'float' => is_float($value),
'object' => is_object($value),
default => false,
}
: is_a($value, $expected);
if($isValid){
$template->$key = $value;
}
}
}
}
}else{
foreach ($params as $key => $value) {
if ($value !== null && property_exists($template, $key)) {
$template->$key = $value;
}
}
}
// foreach ($params as $key => $value) {
// if ($value !== null && property_exists($template, $key)) {
// $template->$key = $value;
// }
// }
Poznámky: ReflectionNamedType zachytí jednoduché + nullable typy, což plně postačuje, pro union typy si vývojář zajistí vlastní injektáž.
Praktické použití: využije se pro definici vlastního objektu $user, pokud vestavěný nette nevyhovuje, v šabloně je ideální mít DTO readonly objekty separované od autentikační logiky.
Dík za případné komentáře. PR: https://github.com/…ion/pull/352
Editoval m.brecher (16. 7. 23:45)