0

My set-up comprises a lib folder with classes and a view folder with PHP files, that produce output. The views are imported inside a View class similar to this:

class View {

    public function render(string $basename, Array $params) : string {
        extract($params, EXTR_PREFIX_INVALID, 'v');
        ob_start();
        include sprintf('%s/views/%s.php', dirname(__DIR__), $basename);
        $out = ob_get_contents();
        ob_end_clean();
        return $out;
    }

}

I have basically two problems with Psalm in this situation:

  1. For View::render it reports a UnresolvableInclude. I can even type the $basename with something like

     @param "view1"|"view2"|"about" $basename
    

    without effect. The unresolvable include remains.

  2. The extract() puts the content of $params in the local scope, where the view files are included. This allows me to have

     <?=escape($foo)?>
    

    “tags” in my view files with $params === ['foo' => 'bar']. However, Psalm doesn’t catch up on this and reports a lot of UndefinedGlobalVariable problems.

My question: How can I tell psalm about the view files and the variables? Or alternatively, how can I re-structure this code so that psalm can test it for me?

Boldewyn
  • 81,211
  • 44
  • 156
  • 212
  • 1
    Technically your view is just a method that takes a bunch of variables and returns a string. So why not make it actually a method like here https://psalm.dev/r/66898ee87f ? – weirdan Mar 20 '21 at 23:51
  • Thank you for that idea! Yes, technically that would work for me. I hesitate a bit, because it seems like a lot of OOP boilerplate, when all I want is a bit of good old-fashioned tag-soup PHP and `include_once "partial/component.php";` in a somewhat controlled manner. I assume, Psalm is the wrong tool to check the views then? It works like a charm, though, checking the OOP part of my code. But maybe I try to use a skrewdriver to handle a nail... – Boldewyn Mar 22 '21 at 20:09
  • Thinking a bit harder about that, maybe I can minimize the boilerplate with plain functions... something like `function part1(string $foo, int $bar) { ?> html ... ... html – Boldewyn Mar 22 '21 at 20:23

1 Answers1

1

There's a demo TemlateChecker plugin in Psalm's repo that seems to do something similar: it looks at the docblock in the view file for the tag like @variablesfrom ClassName::method and makes them available in the template file. Or just properties on $this variable from that method, not sure. It's also mentioned in Psalm docs: Checking non-PHP files.

Alternatively, you could wrap your template into a minimal method/function as technically view is just a function that takes a bunch of variables and returns a string: https://psalm.dev/r/66898ee87f

<?php class HomePageView {  // view starts here
    /** @param list<string> $sections */
    public function render(
        string $title,
        array $sections
    ): string { ob_start();
?>
<html>
    <head>
       <title><?=$title?></title>
    </head>
    <body>
    <?php foreach ($sections as $section): ?>
        <section><?=$section?></section>
    <?php endforeach; ?>
    </body>
</html>

<?php return ob_get_contents(); }} // view ends here ?>

This way any tool that analyzes code (including Psalm, but not limited to) would be able to understand it.

weirdan
  • 2,499
  • 23
  • 27