I have already written a few posts regarding phpstan-dba and rexstan. On a hot sunday in june πŸ˜…, I don’t had a chance leaving the house, so I decided to work on these 2 projects.

You can get some inspiration in a real world example on how to integrate phpstan-dba for use with a legacy api (aka a non-standard db access api).

integrating phpstan-dba and rexstan

As rexstan is meant to be a one click solution to get PHPStan based code analysis into the REDAXO CMS for users not able to do the tool setup, a good next step was enhance the analysis with data from the database.

Since REDAXO CMS provides its own database access layer, I had to re-use existing phpstan-dba rules for this context.

The rex_sql api was designed back in 2010 without static analysis in mind. I am pretty sure, we would built the class very differently today, but people are used to it now, and it is still pretty similar with what we had even before in REDAXO 4.x times. One challenge this brings with it, is that rex_sql uses the same api for working with prepared statements and regular non-prepared queries. Thats the reason why the phpstan-dba config file needs to be a bit more complicated, then usually.

With this logic applied, rexstan is able to detect syntax errors in queries e.g. given to the rex_sql->setQuery() class:

PHPStorm screenshot showing a phpstan-dba reported sql query error

The required configuration is described in more detail in a dedicated phpstan-dba doc chapter.

Utils: rex::getTable() and rex::getTablePrefix()

In REDAXO it is common to use small utility methods to build the sql query. These methods return a concatenation of a hardcoded pre-configured string and the given arguments.

phpstan-dba would skip queries containing calls to these methods as they will inject a non-constant value, not known at analysis time.

In 99% of the cases the default table prefix, which is rex_ is used. So adding a DynamicStaticMethodReturnTypeExtension for these was they way forward.

Making methods return a ConstantStringType allows the query analysis to detect the actual query string being executed. Based on this additional PHPStan-Extension information, even query strings containing calls to rex::getTable() or rex::getTablePrefix() turn analyzable:

$db = rex_sql::factory();
$db->setQuery(
  'select * from ' . rex::getTablePrefix() . 'article_slice '.
  'where article_id=? and clang_id=? and revision=? '.
  'ORDER by ctype_id, priority',
  [$articleId, $clang, $fromRevisionId]
);

The same is true for the legacy rex_sql::escape() and rex_sql::escapeLikeWildcards() method calls. These are covered by a different but similar PHPStan-extension:

$db = rex_sql::factory();
$db->setQuery(
  'select * from ' . rex::getTablePrefix() . 'article '.
  'where article_id='. $db->escape($articleId) .' and clang_id='. $db->escape($clang) .' '.
  'ORDER by ctype_id'
);

Dear REDAXO-User: Obviously prepared statements should be preferred over the legacy escaping api.

next steps

When time allows I have planned to work on getting type-inference done for the rex_sql->getValue() API by re-using part of phpstan-dba. Thats stuff for a different blog post though - stay tuned

Found a bug? Please help improve this article.


<
Previous Post
Analyze your PHPStan baseline
>
Next Post
Type inference for dynamic sql queries