is_resource() checks silently failing after PHP 8.5 upgrade: finfo and FTP
Spent two hours debugging why our file upload MIME detection stopped working after upgrading to PHP 8.5. The culprit was finfo_open() now returning an finfo object rather than a resource. We had a check like if (!is_resource($finfo)) { throw … } which now always passes even when finfo_open fails, because it returns false on failure but an object on success, and is_resource(object) is always false.
The same thing happened with our FTP backup client: ftp_connect() now returns an FTP\Connection object. All the is_resource() guards were wrong in the same way.
The fix is to check for false directly instead of checking is_resource(). But finding all the places in a large codebase takes time.
PHP has been doing this resource-to-object migration for several versions. Most common extensions were migrated in 8.1 and 8.2. PHP 8.5 finished off finfo, FTP, DBA, and ODBC. If you started the project before 8.1 and never audited for is_resource() calls, you probably have a few of these.
PHPStan at level 4 and above flags is_resource() calls on types that are known to return objects now. If you have PHPStan in your pipeline this would have shown up before runtime. We caught it in CI when we updated the PHP stubs.
The correct pattern going forward is: $finfo = finfo_open(FILEINFO_MIME_TYPE); if ($finfo === false) { throw new RuntimeException(‘finfo_open failed’); }. Cleaner than is_resource() was anyway, since you are actually checking the failure condition rather than inferring it.
@wzhang agreed. We also added a wrapper class around finfo that handles the initialization check in the constructor. Makes the error surface cleaner and you only write the false-check once.
```php blocks are runnable.