vimeo / psalm

A static analysis tool for finding errors in PHP applications
https://psalm.dev
MIT License
5.54k stars 660 forks source link

false positive: MissingDependency on Yii2 Active record #10780

Closed mtangoo closed 5 months ago

mtangoo commented 6 months ago

Hi, I have this Yii2 project using active record. for a while running psalm on it would work fine and point out errors. But suddenly it started complaining abount missing dependency. Here is the error I get:

ERROR: MissingDependency - utils/SchoolUtils.php:19:16 - app\models\Subject depends on class or interface yii\db\activerecordinterface that does not exist (see https://psalm.dev/157)
        return Subject::getDb()

Subject is a class extending yii\db\ActiveRecord[1] defined as

<?php

namespace app\models; 

class Subject extends \yii\db\ActiveRecord
{
    //.....
}

psalm.xml:

<?xml version="1.0"?>
<psalm
    errorLevel="8"
    resolveFromConfigFile="true"
    findUnusedCode="false"
    findUnusedBaselineEntry="false"
    phpVersion="8.3"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="https://getpsalm.org/schema/config"
    xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd">
    <projectFiles>
        <directory name="." />
        <file name="web/index.php" />
        <ignoreFiles>
            <directory name="vendor" />
            <directory name="web/assets" />
            <directory name="runtime" />
            <directory name="views" />
            <directory name="migrations" />
            <directory name="mail" />
            <directory name="modules/*/views" /> 
            <directory name="tests" />
            <file name="requirements.php" />
        </ignoreFiles>
    </projectFiles>
</psalm>

I have tried everything I could and could not even make sense of where this might be coming from. One thing I note in the error is this part class or interface yii\db\activerecordinterface that does not exist which is true. yii\db\activerecordinterface does not exist in Yii2 framework. The actual classes are Subject extends \yii\db\ActiveRecord then ActiveRecord extends BaseActiveRecord then BaseActiveRecord extends Model implements ActiveRecordInterface, so it is yii\db\ActiveRecordInterface (note the casing)

That made me think there is a bug somewhere or may be am doing something wrong.

ENV:

Let me know if there is further information that is needed.

[1] https://www.yiiframework.com/doc/api/2.0/yii-db-activerecord

weirdan commented 6 months ago

"vimeo/psalm": "^4.12"

The latest 4.x version was released over two years ago. Even if it had a bug we definitely not going to fix anything in 4.x.

To properly look into your issue, we would need to see it for ourselves. Please try to create a minimal reproducer in a separate repository. To get you started on this I created https://github.com/weirdan/10780 - fork it and tweak it until you can reproduce your issue in that fork.

mtangoo commented 6 months ago

Thanks, I will do.

mtangoo commented 6 months ago

Created sample out of that and there was zero error. Strange but it seems something is broken on my setup

mtangoo commented 6 months ago

While this seems not to be a direct Psalm issue, can you help me suggest where to look at? I cannot understand why would Psalm look for lowacase version i.e. yii\db\activerecordinterface instead of yii\db\ActiveRecordInterface. Moreover none of those classes directly impelement the interface as shown above.

Can you suggest what else I can look at? I'm confused right now.

weirdan commented 6 months ago

The lowercase class name is most likely a red herring. Psalm stores class names in lowercase because symbol names (except variable and constant names) in PHP are case-insensitive, and they do show up in some places, but that's usually nothing more than a cosmetic issue.

weirdan commented 6 months ago

Try running with --debug (or even --debug-by-line) switch - it may provide some valuable information.

mtangoo commented 6 months ago

Ah, thanks. Let me dig that up. I will update of the finding

mtangoo commented 6 months ago

I have observed a strange thing. when I run ./vendor/bin/psalm --no-cache multiple times 1st time 3000+ errors including the above 2nd time 3000+ errors including the above 3rd time 3000+ errors including the above 4th or some random other times: Zero error!

It happens so random, that I cannot make anything repeatable yet. Am still investigating but thought I should share this

weirdan commented 6 months ago

What if you run psalm with --threads=1?

mtangoo commented 6 months ago

./vendor/bin/psalm --threads=1

Results:

------------------------------
3007 errors found
------------------------------
3820 other issues found.
You can display them with --show-info=true
------------------------------

it repeats all the times the same results

mtangoo commented 6 months ago

tried psalm with --no-cache few times it gives a correct result

------------------------------
1 errors found
------------------------------
11191 other issues found.
You can display them with --show-info=true
------------------------------
Psalm can automatically fix 55 of these issues.
Run Psalm again with 
--alter --issues=InvalidReturnType,MissingReturnType,MissingClosureReturnType,InvalidNullableReturnType,MismatchingDocblockReturnType,MissingParamType --dry-run
to see what it can fix.
------------------------------

something hideous is happening somewhere

mtangoo commented 6 months ago

Finally solved it. Thanks to help from @terabytesoftw I had to add psr4 entry for namespace in composer.json. I had assumed my composer.json is all roses and it was not.

"autoload": {
        "psr-4": {
            "app\\": "."
        }
    },

then had to make sure that Yii is included. So the whole new config is

<?xml version="1.0"?>
<psalm
    phpVersion="8.3"
    errorLevel="7"
    resolveFromConfigFile="true"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="https://getpsalm.org/schema/config"
    xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
    findUnusedBaselineEntry="true"
    findUnusedCode="false"
>
    <projectFiles>
        <directory name="." />
        <file name="vendor/yiisoft/yii2/Yii.php" />
        <file name="web/index.php" />
        <ignoreFiles>
            <directory name="vendor" />
            <directory name="web" />
            <directory name="runtime" />
            <directory name="views" />
            <directory name="migrations" />
            <directory name="mail" />
            <directory name="widgets" />
            <directory name="modules/*/views" />
            <directory name="modules/school/modules/*/views" />
            <directory name="tests" />
            <file name="requirements.php" />
        </ignoreFiles>
    </projectFiles>
</psalm>

Thanks for helping, everyone. Cheers!

mtangoo commented 6 months ago

Hi again, I was still facing oscillating error and no error. At least with --no-cache it would mostly work. Then I tried something that seems to have worked so far. I included Yii folder in vendor and it all worked. Like this:

 <projectFiles>
        <directory name="." />
        <directory name="vendor/yiisoft" />
....

The question I have is why do I have to include it manually? Isn't it supposed to be inferred automatically from autoload file? Can you share your thoughts on this? Is adding vendor to ignored directories bad idea?

mtangoo commented 6 months ago

Coincidentally, I found that everytime Psalm misbehaves (i.e spews error in the original post) all I have to do is go on Psalm config, and then change some setting (like error level or something) and then Psalm will behave correctly. It works until it starts misebehaving again of which if I repeat this same process, works again.

I do not know what happens when config change but I can guess that it may be clears cache or something which forces some re-processing or something which somehow solves some strange corruption that happens somewhere I have yet to discover.

Do anyone have any idea what is happening here?

mtangoo commented 5 months ago

Finally after long chasing I got solution to the issue. I needed to include ActiveRecordInterface and Yii paths. It now works fine. Sometime it goes bananas but then works fine when cleaning the cache. But this fixed it all.

I hope it will help someone else struggling with this.

 <projectFiles>
        <file name="vendor/yiisoft/yii2/db/ActiveRecordInterface.php" />
        <file name="vendor/yiisoft/yii2/Yii.php" />
        <directory name="." />
        <ignoreFiles>
            <directory name="vendor" />
            <directory name="runtime" />
            <directory name="tests" />
            <directory name="mail" />
            <directory name="views" />
            <directory name="widgets" />
            <directory name="modules/*/views" />
            <file name="requirements.php" />
        </ignoreFiles>
    </projectFiles>