Skip to content

Instantly share code, notes, and snippets.

@zdenekdrahos
Created December 22, 2016 14:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zdenekdrahos/9856da19f42832bcfa6115004ab778de to your computer and use it in GitHub Desktop.
Save zdenekdrahos/9856da19f42832bcfa6115004ab778de to your computer and use it in GitHub Desktop.
phpstan errors?

phpqa

phpqa --analyzedDirs src/phpstan --output cli --tools phpstan

Output

 ------ -------------------------------------------------------------------------- 
  Line   phpstan/MethodAnnotations.php                                             
 ------ -------------------------------------------------------------------------- 
  10     Call to an undefined method Whatever\Swift_SmtpTransport::setUsername().  
  14     Call to an undefined method Whatever\Swift_SmtpTransport::setUsername().  
 ------ -------------------------------------------------------------------------- 

 ------ ----------------------------------------------------------- 
  Line   phpstan/Proxy.php                                          
 ------ ----------------------------------------------------------- 
  29     Property Whatever\Proxy::$pdo (null) does not accept PDO.  
  30     Cannot call method setAttribute() on null.                 
 ------ ----------------------------------------------------------- 

 ------ ------------------------------------------------------------------------------ 
  Line   phpstan/Singleton.php                                                         
 ------ ------------------------------------------------------------------------------ 
  14     Static property Whatever\Singleton::$facade (null) does not accept stdClass.  
 ------ ------------------------------------------------------------------------------ 

                                                                                                                        
 [ERROR] Found 5 errors        
<?php
namespace Whatever;
// 10 Call to an undefined method Whatever\Swift_SmtpTransport::setUsername().
// 14 Call to an undefined method Whatever\Swift_SmtpTransport::setUsername().
class MethodAnnotations
{
public function __invoke()
{
Swift_SmtpTransport::newInstance()
->setUsername('test@example.com');
$transport = new Swift_SmtpTransport();
$transport->setUsername('test@example.com');
}
}
/**
* https://github.com/swiftmailer/swiftmailer/blob/de19df332219d73a2704525ba75aabd7dfaa303b/lib/classes/Swift/SmtpTransport.php#L11
* Sends Messages over SMTP with ESMTP support.
*
* @author Chris Corbyn
*
* @method Swift_SmtpTransport setUsername(string $username) Set the username to authenticate with.
* @method string getUsername() Get the username to authenticate with.
* @method Swift_SmtpTransport setPassword(string $password) Set the password to authenticate with.
* @method string getPassword() Get the password to authenticate with.
* @method Swift_SmtpTransport setAuthMode(string $mode) Set the auth mode to use to authenticate.
* @method string getAuthMode() Get the auth mode to use to authenticate.
*/
class Swift_SmtpTransport
{
/**
* @return Swift_SmtpTransport
*/
public static function newInstance()
{
return new self();
}
}
<?php
namespace Whatever;
use PDO;
// 29 Property Whatever\Proxy::$pdo (null) does not accept PDO.
// 30 Cannot call method setAttribute() on null.
class Proxy
{
private $connectionString;
/** @var PDO */
private $pdo;
public function __construct($connectionString)
{
$this->connectionString = $connectionString;
}
public function getPDO()
{
$this->loadPDO();
return $this->pdo;
}
private function loadPDO()
{
if (is_null($this->pdo)) {
$this->pdo = new PDO($this->connectionString);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
}
}
}
<?php
namespace Whatever;
// 14 Static property Whatever\Singleton::$facade (null) does not accept stdClass.
class Singleton
{
/** @var \stdClass */
private static $facade;
public static function getInstance()
{
if (is_null(self::$facade)) {
self::$facade = new \stdClass();
}
return self::$facade;
}
}
@ondrejmirtes
Copy link

ondrejmirtes commented Dec 22, 2016

Call to an undefined method Whatever\Swift_SmtpTransport::setUsername()

PHPStan does not yet read method/property annotations on classes yet. It will be possible in some future version through regular expression configuration. The problem is that there's no standard, everyone writes these annotations in a different way and sometimes they are even missing some crucial information like arguments or return types.

For now, you can fix this by implementing your own class reflection extension. Here's a discussion about it.

The other errors are caused by the fact that in PHPStan, you can "cast" any expression to a different type using instanceof and is_* functions. So inside the is_null branch, PHPStan thinks that $this–>pdo and self::$facade have type null and nothing else can be assigned to them. It also works with negated condition:

if (!is_int($foo)) {
  // early termination - throw or return
  throw new \Exception();
}

// I'm sure $foo is integer from now on

I now realize that in case of properties, this is not ideal, but not yet sure how to solve this. As a quick fix, you can use strict comparison with null instead:

        if ($this->pdo === null) {
            $this->pdo = new PDO($this->connectionString);
            $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
       }

@ondrejmirtes
Copy link

I will document all the type-specifying features soon.

@zdenekdrahos
Copy link
Author

zdenekdrahos commented Dec 23, 2016

@ondrejmirtes You don't have a reason to add annotation to 3rd party code, because PHPStan should not analyze 3rd party code

I am talking about errors in my tests, not in 3rd party code.
And I really don't want to add annotation to every test with mocks (previously I thought about adding annotations to ObjectProphecy etc.).

parameters:
    universalObjectCratesClasses:
        - Dibi\Row
        - Ratchet\ConnectionInterface
    ignoreErrors:
        - '#Call to an undefined method Prophecy\\Prophecy\\ObjectProphecy::[a-zA-Z0-9_]+\(\)#' 
        - '#Access to an undefined property Mockista\\MethodInterface::\$[a-zA-Z0-9_]#'
        - '#Access to an undefined property Mockista\\MockInterface::\$[a-zA-Z0-9_]#'
        - '#Access to an undefined property PHPUnit_Framework_MockObject_MockObject::\$[a-zA-Z0-9_]+#'
        - '#Call to an undefined method PHPUnit_Framework_MockObject_MockObject::[a-zA-Z0-9_]+\(\)#'

@ondrejmirtes
Copy link

@zdenekdrahos Well, the tests situation is ideally solved either by adding the regexps to ignoreErrors (precisely what you did) or by documenting your code correctly - documenting your variables and properties by @var \Foo|\ PHPUnit_Framework_MockObject_MockObject (which will switch them to mixed type and PHPStan will not perform any checks on them). I don't see a third solution with current features.

It will be possible to create a PHPUnit/Prophecy/Mockista extension in the future that will solve this for you correctly. I am also planning to support union types (so that Foo|Bar does not result in mixed but behaves and checks everything correctly), but there are yet some decisions and technical problems to overcome.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment