In the previous post, we finished the conversion of TWIG to PHP, run PHPStan on temporary PHP file and got list of found errors. We've done a full circle, and PHPStan analyses our TWIG templates.
I've shared the intro post on Reddit, that sparked many exciting questions.
Today we'll answer one of them.
This is a great question! We skipped this piece to complete the TWIG rendering itself first and keep your cognitive load focused. Now we have time and space to bring the answer.
First, let's define what PHP and TWIG files we work with. We render a template with 1 parameter:
use App\Meal;
return $this->render('meal.twig', [
'meal' => new Meal()
]);
The TWIG template contains one method call:
{{ meal.title }}
Using TWIG and php-parser we compile TWIG into nice and clean PHP:
echo $meal->getTitle();
Is this clear? Now we can level up.
$meal
come From?Looking at the code, the $meal
looks like coming out of nowhere. Where is it defined? In TWIG, these variables are created from a $context
array, passed as a parameter of the main doDisplay()
method in the compiled template class.
This $context
is an array made mostly of 2nd argument in $this->render()
method.
In our compiled PHP, we get the $meal
variable from the $context
array:
public function doDisplay(array $context)
{
$meal = $context['meal'];
echo $meal->getTitle();
}
And that's how TWIG variables are born in compiled PHP.
$meal
Variable?Now PHPStan and we know the variable $meal
exists and comes from some $context
array. But the type is still mixed
. How do we know $meal
is App\Meal
?
Let's back up a little bit and look into first place the type appeared - in our controller:
use App\Meal;
return $this->render('meal.twig', [
'meal' => new Meal()
]);
Here we see that the meal
string is the type of App\Meal
. Could we add this type somehow to meal.twig
?
Not so fast! The templates are reusable so that another developer can render them in the following way:
use App\Meal;
return $this->render('meal.twig', [
'meal' => 'Dinner'
]);
Here the $meal
variable is a type of string
and the template {{ meal.title }}
will crash.
So to answer the question - the type depends on the place it's rendered in PHP. The same way PHPStan does not analyze traits standalone, but only in the context of specific class.
We see that rendering the same template in different places can result in different types. Also, we should not forget, the PHPStan sees only the final compiled PHP file - it has no idea about the controller the template is rendered in.
Now that we know the rules of the game, the plan is pretty straightforward:
Let's take step by step in our practical example.
We collect types at the exact moment we see the $twig->render()
method in PHP code.
use App\Meal;
return $this->render('meal.twig', [
'meal' => new Meal()
]);
The 2nd argument is an array, and we can traverse that array and detect the type with PHPStan's $scope
.
At the end of this step, we'll have an array with a variable name and its type:
meal
=> PHPStan\Type\ObjectType
of App\Meal
We've already covered this process in previous parts:
The TWIG is now compiled to PHP, and we know the types of variables. But the PHPStan has still no idea about types:
public function doDisplay(array $context)
{
$meal = $context['meal'];
echo $meal->getTitle();
}
How do we help PHPStan in standard PHP code if we know about some types that are not clear from the code?
Like this:
public function doDisplay(array $context)
{
+ /** @var \App\Meal $meal */
$meal = $context['meal'];
echo $meal->getTitle();
}
We'll automate this process again with php-parser
. Now the PHPStan can analyze the code with all the known types.
Happy coding!
Do you learn from my contents or use open-source packages like Rector every day?
Consider supporting it on GitHub Sponsors.
I'd really appreciate it!