psalm
psalm copied to clipboard
UnresolvableInclude when using variable - second
Psalm can't follow the include and returns "UnresolvableInclude: Cannot resolve the given expression to a file path"
# file Settings.php
define ('ROOT', './src' );
class Settings {
public const FOLDERS_PATH = [ 'templates' => ROOT . '/templates' ]
}
# file Other.php
$file_with_functions = Settings::FOLDERS_PATH['templates'] . '/myfile.php';
require_once $file_with_functions;
But this follows the include with no error:
# file Settings.php
define ('ROOT', './src' );
# file Other.php
$file_with_functions = ROOT . '/templates/myfile.php';
require_once $file_with_functions;
As a result, Psalm report that the functions I defined in my $file_with_functions file are not found...
Hey @jcvignoli, can you reproduce the issue on https://psalm.dev? These will be used as phpunit tests when implementing the feature or fixing this bug.
Another variation: https://psalm.dev/r/f05f18c046 (note the type of after_concat
element)
Funny thing though, if you uncomment 'after' => self::AFTER
- it works as expected: https://psalm.dev/r/003ab2965b
I found these snippets:
https://psalm.dev/r/f05f18c046
<?php
class C {
public const BEFORE = 'long time ago';
public const ARR = [
'before_concat' => self::BEFORE . ' in a galaxy far away',
'after_concat' => self::AFTER . ' after the Sun explodes',
// 'after' => self::AFTER,
];
public const AFTER = 'in a distant future';
}
$_d = C::ARR;
/** @psalm-trace $_d */;
Psalm output (using commit 4b2c698):
INFO: Trace - 13:24 - $_d: array{after_concat: string, before_concat: 'long time ago in a galaxy far away'}
https://psalm.dev/r/003ab2965b
<?php
class C {
public const BEFORE = 'long time ago';
public const ARR = [
'before_concat' => self::BEFORE . ' in a galaxy far away',
'after_concat' => self::AFTER . ' after the Sun explodes',
'after' => self::AFTER,
];
public const AFTER = 'in a distant future';
}
$_d = C::ARR;
/** @psalm-trace $_d */;
Psalm output (using commit 4b2c698):
INFO: Trace - 13:24 - $_d: array{after: 'in a distant future', after_concat: 'in a distant future after the Sun explodes', before_concat: 'long time ago in a galaxy far away'}
So the simplest test case is: https://psalm.dev/r/7fa1a03bd7
This is not a bug per se, as the inferred type is correct, just not as precise as it could be.
I found these snippets:
https://psalm.dev/r/7fa1a03bd7
<?php
class A {
const C = ['c is ' . self::B];
const B = 'b';
}
$_ = A::C;
/** @psalm-check-type-exact $_ = list{'c is b'} */;
Psalm output (using commit 4b2c698):
ERROR: CheckType - 7:51 - Checked variable $_ = list{'c is b'} does not match $_ = list{string}
@jcvignoli in your second example, how does Settings.php
get loaded? In the first example autoload could load it (because the Settings
class is referenced), but it's not immediately clear what's going on in the case you say is working.
@weirdan I have an autoload file loading the class using spl_autoload_register().
Actually, the
define ('ROOT', './src' );
is not in Settings.php, but in a different config file called before the autoload.
I tried to simplify as much as possible, but the file Others.php is a class with a method, which basically does this:
protected function method( string $column ): array {
require_once Settings::FOLDERS_PATH['templates'] . '/myfile.php';
/** @psalm-suppress UndefinedFunction -- it actually exists! */
$replacement_list = returnSelectedText( 'title' );
return $replacement_list[ $column ];
}
Since myfile.php is not found (psalm throws a warning), it doesn't find the function returnSelectedText() in myfile.php.
Should I replace
Settings::FOLDERS_PATH['templates'] . '/myfile.php';
with
ROOT . '/templates/myfile.php';
in the method, the function is found by psalm.
Settings::FOLDERS_PATH['templates'] is a dirty class including various paths defined as follows:
class Settings {
public const MAIN = [
'ASSETS_PATH' => ROOT . '/assets',
];
public const FOLDERS_PATH = [
'pics' => self::MAIN['ASSETS_PATH'] . '/pics',
'js' => self::MAIN['ASSETS_PATH'] . '/js',
'css' => self::MAIN['ASSETS_PATH'] . '/css',
'templates' => ROOT . '/templates',
'class' => ROOT . '/class',
];
}
Hope it helps to grasp what's going on.