Code Monkey home page Code Monkey logo

phpstan-doctrine's Introduction

Doctrine extensions for PHPStan

Build Latest Stable Version License

This extension provides following features:

  • DQL validation for parse errors, unknown entity classes and unknown persistent fields. QueryBuilder validation is also supported.
  • Recognizes magic findBy*, findOneBy* and countBy* methods on EntityRepository.
  • Validates entity fields in repository findBy, findBy*, findOneBy, findOneBy*, count and countBy* method calls.
  • Interprets EntityRepository<MyEntity> correctly in phpDocs for further type inference of methods called on the repository.
  • Provides correct return for Doctrine\ORM\EntityManager::getRepository().
  • Provides correct return type for Doctrine\ORM\EntityManager::find, getReference and getPartialReference when Foo::class entity class name is provided as the first argument
  • Adds missing matching method on Doctrine\Common\Collections\Collection. This can be turned off by setting parameters.doctrine.allCollectionsSelectable to false.
  • Also supports Doctrine ODM.
  • Analysis of discrepancies between entity column types and property field types. This can be relaxed with the allowNullablePropertyForRequiredField: true setting.
  • Provides return type for Doctrine\ORM\Query::getResult, getOneOrNullResult, getSingleResult, toIterable and execute in HYDRATE_OBJECT mode (see below).

Installation

To use this extension, require it in Composer:

composer require --dev phpstan/phpstan-doctrine

If you also install phpstan/extension-installer then you're all set!

Manual installation

If you don't want to use phpstan/extension-installer, include extension.neon in your project's PHPStan config:

includes:
    - vendor/phpstan/phpstan-doctrine/extension.neon

If you're interested in DQL/QueryBuilder validation, include also rules.neon (you will also need to provide the objectManagerLoader, see below):

includes:
    - vendor/phpstan/phpstan-doctrine/rules.neon

Configuration

If your repositories have a common base class, you can configure it in your phpstan.neon and PHPStan will see additional methods you define in it:

parameters:
	doctrine:
		ormRepositoryClass: MyApp\Doctrine\BetterEntityRepository
		odmRepositoryClass: MyApp\Doctrine\BetterDocumentRepository

You can opt in for more advanced analysis by providing the object manager from your own application. This will enable DQL validation:

parameters:
	doctrine:
		objectManagerLoader: tests/object-manager.php

Example for Symfony 4:

// tests/object-manager.php

use App\Kernel;

require __DIR__ . '/../config/bootstrap.php';
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$kernel->boot();
return $kernel->getContainer()->get('doctrine')->getManager();

Example for Symfony 5:

// tests/object-manager.php

use App\Kernel;
use Symfony\Component\Dotenv\Dotenv;

require __DIR__ . '/../vendor/autoload.php';

(new Dotenv())->bootEnv(__DIR__ . '/../.env');

$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$kernel->boot();
return $kernel->getContainer()->get('doctrine')->getManager();

Query type inference

This extension can infer the result type of DQL queries when an objectManagerLoader is provided.

Examples:

$query = $entityManager->createQuery('SELECT u FROM Acme\User u');
$query->getResult(); // array<Acme\User>

$query = $entityManager->createQuery('SELECT u.id, u.email, u.name FROM Acme\User u');
$query->getResult(); // array<array{id: int, email: string, name: string|null}>

$query = $entityManager->createQuery('
    SELECT u.id, u.email, COALESCE(u.name, "Anonymous") AS name
    FROM   Acme\User u
');
$query->getSingleResult(Query::HYDRATE_OBJECT); // array{id: int, email: string, name: string}>

$query = $entityManager->createQueryBuilder()
    ->select('u')
    ->from(User::class, 'u')
    ->getQuery();
$query->getResult(); // array<Acme\User>

Queries are analyzed statically and do not require a running database server. This makes use of the Doctrine DQL parser and entities metadata.

Most DQL features are supported, including GROUP BY, DISTINCT, all flavors of JOIN, arithmetic expressions, functions, aggregations, NEW, etc. Sub queries and INDEX BY are not yet supported (infered type will be mixed).

Supported methods

The getResult method is supported when called without argument, or with the hydrateMode argument set to Query::HYDRATE_OBJECT:

$query = $entityManager->createQuery('SELECT u FROM Acme\User u');

$query->getResult(); // array<User>

$query->getResult(Query::HYDRATE_OBJECT); // array<User>

The methods getOneOrNullResult, getSingleResult, toIterable, and execute are supported when the hydrateMode argument is explicitly set to Query::HYDRATE_OBJECT:

$query = $entityManager->createQuery('SELECT u FROM Acme\User u');

$query->getOneOrNullResult(); // mixed

$query->getOneOrNullResult(Query::HYDRATE_OBJECT); // User

This is due to the design of the Query class preventing from determining the hydration mode used by these functions unless it is specified explicitly during the call.

Problematic approaches

Not every QueryBuilder can be statically analysed, here are few advices to maximize type inferring:

  • Do not pass QueryBuilder to methods
  • Do not use dynamic expressions in QueryBuilder methods (mainly in select/join/from/set)

You can enable reporting of places where inferring is unavailable by:

parameters:
	doctrine:
		reportDynamicQueryBuilders: true

Custom types

If your application uses custom Doctrine types, you can write your own type descriptors to analyse them properly. Type descriptors implement the interface PHPStan\Type\Doctrine\Descriptors\DoctrineTypeDescriptor which looks like this:

<?php

public function getType(): string;

public function getWritableToPropertyType(): Type;

public function getWritableToDatabaseType(): Type;
  • The getType() method simply returns the class name of the custom type.
  • The getWritableToPropertyType() method returns the PHPStan type that the custom type will write into the entity's property field. Basically it is the return type of the custom type's convertToPHPValue() method.
  • The getWritableToDatabaseType() method returns the PHPStan type that can be written from the entity's property field into the custom type. Again, basically it's the allowed type for the custom type's convertToDatabaseValue()'s first argument.

Generally, at least for most of Doctrine's native types, these last two methods will return the same type, but it is not always the case. One example would be the datetime type, which allows you to set any \DateTimeInterface into to property field, but will always contain the \DateTime type when loaded from the database.

Nullable types

Type descriptors don't have to deal with nullable types, as these are transparently added/removed from the descriptor's types as needed. Therefore you don't have to return the union type of your custom type and NullType from the descriptor's methods, even if your custom type allows null.

ReflectionDescriptor

If your custom type's convertToPHPValue() and convertToDatabaseValue() methods have proper typehints, you don't have to write your own descriptor for it. The PHPStan\Type\Doctrine\Descriptors\ReflectionDescriptor can analyse the typehints and do the rest for you.

Registering type descriptors

When you write a custom type descriptor, you have to let PHPStan know about it. Add something like this into your phpstan.neon:

services:
	-
		class: MyCustomTypeDescriptor
		tags: [phpstan.doctrine.typeDescriptor]

	# in case you are using the ReflectionDescriptor
	-
		factory: PHPStan\Type\Doctrine\Descriptors\ReflectionDescriptor('MyApp\MyCustomTypeName')
		tags: [phpstan.doctrine.typeDescriptor]

phpstan-doctrine's People

Contributors

arnaud-lb avatar craigfrancis avatar curry684 avatar enumag avatar herndlm avatar janedbal avatar jeroennoten avatar julienfalque avatar kamil-zacek avatar khartir avatar kocal avatar lcobucci avatar localheinz avatar lookyman avatar matks avatar mbrodala avatar mcfedr avatar mhujer avatar ondrejmirtes avatar pepakriz avatar renovate-bot avatar renovate[bot] avatar ruudk avatar staabm avatar stof avatar thomaslandauer avatar tomasvotruba avatar vasekpurchart avatar vhenzl avatar vincentlanglet avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

phpstan-doctrine's Issues

Error with entity aliases

Hello, although I define an object-manager, the plugin cannot correctly process the entity aliases.

Example:

$entityManager->getRepository('App\Entity\User'); // works

$entityManager->getRepository('Entity:User'); // error unknown class Entity:User

Thanks

Uncaught PHPStan\ShouldNotHappenException RepositoryMethodCallRule.php

Hi,

I got an issue (stack trace at the end):

Uncaught PHPStan\ShouldNotHappenException: Please provide the "objectManagerLoader" setting for magic repository Doctrine\ORM\EntityRepository::findOneBy() RepositoryMethodCallRule.php:68

This happens when I have following property and findOneByNumber method using findOneBy method of EntityRepository. The EntityRepository is subclass of Doctrine\ORM\EntityRepository.

/** @var \Kdyby\Doctrine\EntityRepository|\Doctrine\ORM\EntityRepository<Invoice> */
private $repository;

/**
* OrderRepository constructor.
* @param \Kdyby\Doctrine\EntityManager $em
 */
public function __construct(\Kdyby\Doctrine\EntityManager $em)
{
	$this->em = $em;
	$this->repository = $em->getRepository(Invoice::class);
}

/**
	 * @param int $number
	 *
	 * @return null|Invoice
	 */
	public function findOneByNumber($number)
	{
		return $this->repository->findOneBy(['number' => $number]);
	}
Stack trace:
#0 C:\Web\Apps\zvz-new\home\libs\vendor\phpstan\phpstan\src\Analyser\Analyser.php(154): PHPStan\Rules\Doctrine\ORM\RepositoryMethodCallRule->processNode(Object(PhpParser\Node\Expr\MethodCall), Object(PHPStan\Analyser\Scope))
#1 C:\Web\Apps\zvz-new\home\libs\vendor\phpstan\phpstan\src\Analyser\NodeScopeResolver.php(1698): PHPStan\Analyser\Analyser->PHPStan\Analyser\{closure}(Object(PhpParser\Node\Expr\MethodCall), Object(PHPStan\Analyser\Scope))
#2 C:\Web\Apps\zvz-new\home\libs\vendor\phpstan\phpstan\src\Analyser\NodeScopeResolver.php(1146): PHPStan\Analyser\NodeScopeResolver->callNodeCallbackWithExpression(Object(Closure), Object(PhpParser\Node\Expr\MethodCall), Object(PHPStan\Analyser\Scope), Object(PHPSta in C:\Web\Apps\zvz-new\home\libs\vendor\phpstan\phpstan-doctrine\src\Rules\Doctrine\ORM\RepositoryMethodCallRule.php on line 68

I can provide more info if needed.

Thanks!

Feature request: Check if dynamic binding is used correctly

I'm looking at this primarily as a security feature. Ensuring that prepared statements are used correctly by binding the value or parameter accordingly.

For example:

This is the preferred way of doing a native query:

$sql = <<<SQL
SELECT u.*
FROM `user` AS u
WHERE u.email = :email
SQL;

$em = $this->getEntityManager();
$connection = $em->getConnection();
$statement = $connection->prepare($sql);
$statement->bindValue('email', $email->toString());
$statement->execute();

return $statement->fetchAll();

As opposed to not binding a value:

$sql = <<<SQL
SELECT u.*
FROM `user` AS u
WHERE u.email = {$email->toString()}
SQL;

$em = $this->getEntityManager();
$connection = $em->getConnection();
$statement = $connection->prepare($sql);
$statement->execute();

return $statement->fetchAll();

The same should apply when using the ResultSetMappingBuilder

        $rsm = new ResultSetMappingBuilder($this->_em);
        $rsm->addRootEntityFromClassMetadata(User::class, 'u');
        $query = $this->_em->createNativeQuery($sql, $rsm);

        $query->setParameters([
            'email' => $email->toString(),
        ]);

        return $query->getResult();

As well as when using the QueryBuilder:

        $qb = $this->createQueryBuilder('u')
            ->where('u.email = :email')
            ->setParameter('email', $email->toString())
            ->getQuery();

        return $qb->execute();

How to remove CallStaticMethodsRule

I want to use phpstan in laravel project with level 0 and need to remove PHPStan\Rules\Methods\CallStaticMethodsRule, how can I create the same config as level 0, but without this rule?

I tried to copy whole conf/ dir and tried only to remove this rules and pass this config to -c, but I still get exceptions.

Manage findOneBy and findBy

This:

$dnsDomain = $dnsEntityManager->getRepository('PowerDNSBundle:PowerDNSDomain')->findOneBy([
    'ownerId' => $user->getId()
]);
if (!$dnsDomain) {
    throw new \RuntimeException("Unable to find DNS domain of user {$user->getUsername()}");
}
$client->request('DELETE', $this->getUrl('api_delete_dns', [
    'domain' => $dnsDomain->getId(),
]));

Should work because I check if $dnsDomain is not null. But I have:

Call to an undefined method object::getId().

Certainly because only the find method is managed.

Detect invalid annotation

I have several errors in the following example, none is detected by phpstan-doctrine currently.

Versions

phpstan/phpstan                       0.12.4       
phpstan/phpstan-doctrine              0.12.8       
octrine/annotations                   v1.8.0           
doctrine/orm                          v2.6.6      

Errors expected

  • repositoryClass does not exist
  • InheritanceType value is invalid
  • DiscriminatorColumn key "namse" does not exist
  • ORM\DiscriminatorsadMap does not exist
  • type "sting" does not exists.

Snippet

/**
 * @ORM\Entity(repositoryClass="EmailRepositoryInterface")
 * @ORM\InheritanceType("SINGLsdTABLE")
 * @ORM\DiscriminatorColumn(namse="email_type", type="string")
 * @ORM\DiscriminatorsadMap({
 *     "default": "Full\NameSpace\Default",
 *     "device_email": "Full\NameSpace\DeviceEmail"
 * })
 */

    /**
     *
     * @ORM\Column(type="sting")
     */

phpstan.neon

parameters:
    autoload_files:
        - bootstrap/autoload.php
includes:
    - vendor/phpstan/phpstan-doctrine/extension.neon

Support for ODM

Is ODM supported? I guess not. Do you plan to add the support?

Config-based support for custom repository classes

As the PoC of #18 seems stalled to resolve custom repository classes based on the mapping, a simpler solution could be implemented in the meantime (even though it is not as good for DX as it requires more work). Instead of configuring a single repository class used by all entities, we could configure a mapping of entity classes to their repositories (the existing option can of course still be kept for the fallback for entities not present in the map):

parameters:
        doctrine:
                customRepositories:
                        MyApp\Entity\User: MyApp\Doctrine\UserRepository
                        MyApp\Entity\Team: MyApp\Doctrine\TeamRepository
                repositoryClass: MyApp\Doctrine\BetterEntityRepository

What do you think about that solution ?

Internal error: Argument 5 passed to PHPStan\Analyser\Scope::enterClassMethod() must be of the type string or null, boolean given

phpstan/phpstan#2148

PHP Fatal error:  Uncaught TypeError: Argument 5 passed to PHPStan\Analyser\Scope::enterClassMethod() must be of the type string or null, boolean given, called in vendor/phpstan/phpstan-doctrine/src/Type/Doctrine/QueryBuilder/QueryBuilderMethodDynamicReturnTypeExtension.php on line 180 and defined in phar://vendor/phpstan/phpstan-shim/phpstan.phar/src/Analyser/Scope.php:1265
Stack trace:
#0 vendor/phpstan/phpstan-doctrine/src/Type/Doctrine/QueryBuilder/QueryBuilderMethodDynamicReturnTypeExtension.php(180): PHPStan\Analyser\Scope->enterClassMethod(Object(PhpParser\Node\Stmt\ClassMethod), Array, NULL, NULL, false, false, false)
#1 vendor/phpstan/phpstan-doctrine/src/Type/Doctrine/QueryBuilder/QueryBuilderMethodDynamicReturnTypeExtension.php(108): PHPStan\Type\Doctrine\QueryBuilder\QueryBuilderMethodDynamicReturnTypeExtension->findQueryBuilderTypesInCalledMetho in phar://vendor/phpstan/phpstan-shim/phpstan.phar/src/Analyser/Scope.php on line 1265` 

Doctrine and float and string

Hi, is it feasible for the plugin to detect Doctrine's behavior with respect to floats being handled as a string? Example:

/**
 * @Entity
 */
class Demo
{
    /** @Column(type="decimal", precision=10, scale=2) */
    private $price;

    /**
     * Set price.
     *
     * @param string $number
     *
     * @return Demo
     */
    public function setPrice($number)
    {
        $this->price = $number;

        return $this;
    }
}

$demo = new Demo();
$demo->setPrice(0.0);

Error output: Parameter #1 $number of method Demo::setPrice() expects string, float given

Note: The Doctrine orm: generate-entities console command generates the method with its parameter as string.

Thanks.

Crash: Service 'rules.56' (type of PHPStan\Rules\Doctrine\ORM\DqlRule): Service of type PHPStan\Type\Doctrine\ObjectMetadataResolver needed by $objectMetadat aResolver in __construct() not found

Hey, I just tried to enable these rules and get this crash:

In Autowiring.php line 190:
                                                                               
  Service 'rules.56' (type of PHPStan\Rules\Doctrine\ORM\DqlRule): Service of  
   type PHPStan\Type\Doctrine\ObjectMetadataResolver needed by $objectMetadat  
  aResolver in __construct() not found. Did you register it in configuration   
  file?                                                                                                                                                    

analyse [--paths-file PATHS-FILE] [-c|--configuration CONFIGURATION] [-l|--level LEVEL] [--no-progress] [--debug] [-a|--autoload-file AUTOLOAD-FILE] [--error-format ERROR-FORMAT] [--memory-limit MEMORY-LIMIT] [--] [<paths>...]

I have these things in composer.json

"phpstan/phpstan": "0.11.2",
"phpstan/phpstan-doctrine": "0.11.1",
"phpstan/phpstan-mockery": "0.11.0",
"phpstan/phpstan-nette": "0.11.0",
"thecodingmachine/phpstan-safe-rule": "0.1.2"

and dev-master nette.

It should be noted that I don't use ORM but ODM.

False positive when entity doesn't have a field but repository has a method

What happens:
If I have a repository with a method findBySomething and the entity doesn't have the property $something I get an error about the missing field when I try to use this method.
E.g.

Call to method App\Repository\ThingsRepository<App\Entity\Thing>::findBySomething() - entity App\Entity\Thing does not have a field named $something.                                                                                                          

What I expect:
No error as the method is present

Bug: return type resolving

Hi,

I'we just noticed that when using 'phpstan-doctrine' extension, there is a weird behaviour/bug with findOneBy methods: doesnt report error on calling non-existing method.

As a proof of concept (not able to share project I work on): https://github.com/msvrtan/phpstan-bug`

When calling a method findOneByUuid, it doesnt understand it's getting a Customer|null back but when calling findXOneByUuid it does: https://travis-ci.com/msvrtan/phpstan-bug/jobs/183766249

I'we also created a PR msvrtan/phpstan-bug#2 that fails https://travis-ci.com/msvrtan/phpstan-bug/builds/103870199 once I remove that Repository does extend EntityRepository...

On the other hand, if I do 'inject' repository as

/* @var \Doctrine\ORM\EntityRepository<\Customer\Customer> */
private $customerRepository;

it does report ...

I know this would be a quick 'fix' to go thru codebase and add those docblocks but it still feels like unexpected behaviour

Support multiple object managers?

In my project I have multiple objectmanagers.

This currently leads to errors like:

Internal error: The class 'X' was not found in the chain configured namespaces ...

because its using the wrong objectManager (the one returned in object-manager.php) for some entities.

How can i make custom repositories work?

Any way i could get rid of such errors? am i missing some configuration step?

43 Call to an undefined method Doctrine\ORM\EntityRepository<x\Entity\Operation>::findFiltered(). 46 Call to an undefined method Doctrine\ORM\EntityRepository<x\Entity\Customer>::findWithFreeFunds(). 71 Call to an undefined method Doctrine\ORM\EntityRepository<x\Entity\BgzDocument>::getFilteredQueryBuilder().

Compatibiliy with EntityRepository::createQueryBuilder

Hello,
It seems that query builders built with \Doctrine\ORM\EntityRepository::createQueryBuilder are not validating DQL, only ones built with \Doctrine\ORM\EntityManagerInterface::createQueryBuilder.

I'm trying to analyse a Symfony 4 project where all queryBuilder are created within repository classes with $this->createQueryBuilder.
Am I missing something ?

Thanks

Call to undefined method Doctrine\ORM\Query\Expr::myCustomMethod()

I have custom Expr class extended from Doctrine\ORM\Query\Expr

I think, PHPStan doesn't know about that class and trigger error

In ExpressionBuilderDynamicReturnTypeExtension.php line 67:
                                                                                                         
  Attempted to call an undefined method named "myCustomMethod" of class "Doctrine\ORM\Query\Expr".

is it because Doctrine\ORM\Query\Expr harcoded in getClass() method?
How to fix that?

Discriminator detection

Hi,

I have an abstract class BasicFile which is used as a Discriminator with Single Table Inheritance.
https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/inheritance-mapping.html#single-table-inheritance

<?php
/**
 * @ORM\Table(name="files")
 * @ORM\Entity
 * @ORM\HasLifecycleCallbacks
 * @ORM\InheritanceType("SINGLE_TABLE")
 * @ORM\DiscriminatorColumn(name="type", type="string")
 * @ORM\DiscriminatorMap({"data" = "DataFile"})
 */
abstract class BasicFile {}

And my entity:

<?php
/*
 * @ORM\Entity
 */
class DataFile extends BasicFile {}

When I initiate a method on the DataFile entity, which is not available on the BasicFile class, i get an error stating the method does not exist.

<?php
$dataFile = new DataFile();
$dataFile->setName('xxx');

Call to an undefined method App\Entity\BasicFile::setName().

Using:
Symfony 5.0.1
doctrine/orm: 2.7.0
phpstan-shim: 0.11.19
phpstan-doctrine: 0.11.6

Entity check error : has no typehint specified

Hi,

I used the new version of Symfony 5, and I want to check my code with phpstan 0.12.2 and phpstan-doctrine 0.12.2.
I have many error in this version, for example :

  Line   Entity/User.php

  18     Property App\Entity\User::$id has no typehint specified.
  23     Property App\Entity\User::$email has no typehint specified.
  28     Property App\Entity\User::$name has no typehint specified.
  33     Property App\Entity\User::$password has no typehint specified.
  38     Property App\Entity\User::$enable has no typehint specified.
  43     Property App\Entity\User::$notification has no typehint specified.

And my Entity

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;

/**
 * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
 */
class User implements UserInterface
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=500)
     */
    private $email;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $name;

    /**
     * @ORM\Column(type="string", length=75)
     */
    private $password;

    /**
     * @ORM\Column(type="boolean")
     */
    private $enable;

    /**
     * @ORM\Column(type="boolean")
     */
    private $notification = false;
}

I used phpstan/extension-installer (I will try before with file add manually in phpstan.neon /var/www/html/vendor/phpstan/phpstan-doctrine/extension.neon and /var/www/html/vendor/phpstan/phpstan-doctrine/rules.neon)

In the previous version I haven't this error type without @var in my code.

Can you help me ?

Thanks

Unable to validate field on findOneBy*

Hi, on my project the extension works great, except for findOneBy* methods-like.

I was unable to reproduce the bug within this repository, but I've created a dedicated one that shows the issue:
https://github.com/Slamdunk/phpstan-doctrine-bug
And this is the output of the build:
https://travis-ci.org/Slamdunk/phpstan-doctrine-bug/builds/638002155#L238-L241
I expect the error on findOneByXyz but not on findOneByUsername.

I've committed both the composer.json and composer.lock so you can deep down the versions, which are:

  1. PHP 7.3.12
  2. PHPStan 0.12.5
  3. PHPStan Doctrine 0.12.9
  4. Doctrine ORM 2.7.0

Feature request: Checking type hint of fields against Doctrine metadata

phpstan-doctrine could check if a field's typehint matches Doctrine's metadata.

In the following example, the plugin could emit a warning telling that int does not match string:

/**
 * @var int
 *
 * @Column(type="string")
 */
 private $var;

In the following examples, the plugin could emit a warning telling that the field is nullable:

/**
 * @var int // Should be nullable
 *
 * @Column(type="integer", nullable=true)
 */
 private $var;

/**
 * @var Foo // Should be nullable
 *
 * @OneToOne(targetEntity="Foo")

Doctrine QueryBulder typehint

Level 5 gives me such warnings: Parameter #4 $condition of method Doctrine\ORM\QueryBuilder::leftJoin() expects string|null, Doctrine\ORM\Query\Expr\Andx given

That happens when you dont use simple string conditions like this ->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, 'p.is_primary = 1'); while joining but when using Expr builder instead. QueryBuilder's join functions document that argument as string|null but it also takes Expr

Do you think I should mark this warning as ignored?

Manage innerJoin not nullable associations

With this PHP code:

$qb = $this->invoiceRepository->createQueryBuilder('i')
    ->select('i, c, ba')
    ->innerJoin('i.customer', 'c', Expr\Join::WITH, 'c.automaticPaymentMethod = :automatic_payment_method')
    ->innerJoin('c.bankAccount', 'ba', Expr\Join::WITH, 'ba.activated = :activated AND ba.mandateSigned = :mandateSigned AND ba.mandateSignedDate IS NOT NULL')

foreach ($invoices as $invoice) {
    $customer = $invoice->getCustomer();
    $bankAccount = $customer->getBankAccount();

    $bankAccount->setFirstDebit(true);
    $this->persist($bankAccount);
}
;

I'll have this error:

Parameter #1 $object of method Doctrine\Common\Persistence\ObjectManagerDecorator::persist() expects object, AppBundle\Entity\BankAccount|null given.

But this is not true as innerJoin will drop customers without a bank account. Am I right?

If I'm right, checking if $bankAccount in this case is overkill and will never match.

Support for Repositories as a Services (Symfony)

Hi, this feature request follows #65 (comment) and #65 (comment).

Since Symfony 4.0, it is possible to define Repositories as as Service by extending from ServiceEntityRepository like this:

<?php

namespace App\Repository\User;

use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\RegistryInterface;

/**
 * @method User|null find($id, $lockMode = null, $lockVersion = null)
 * @method User|null findOneBy(array $criteria, array $orderBy = null)
 * @method User[]    findAll()
 * @method User[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
 */
class UserRepository extends ServiceEntityRepository
{
    public function __construct(RegistryInterface $registry)
    {
        parent::__construct($registry, User::class);
    }
}

And we can accessing it directly from services constructor or constroller's actions with type hinting:

<?php

namespace App\Controller;

use App\Repository\User\UserRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;

class HomeController extends AbstractController
{
    /**
     * @Route("/", name="home")
     */
    public function index(UserRepository $userRepository)
    {
        $user = $userRepository->find(123);
        $user->getFooBar();
    }
}

But there are some issues.

If we use this phpstan extension, calling whatever method on $user that does not exist on App\Entity\User will not fails.

If we don't use it, $userRepository->find* methods will works correctly (due to @method annotations, but they are needed for PHPStorm too) and we will see the error Cannot call method getFooBar() on App\Entity\User|null.

As said in the issue, we can use /** @var App\Repository\UserRepository<App\Entity\User> */ to make phpstan works, but:

  • this is verbose, we have to add it everytime we inject a repository as service dependency
  • this is non sense, since the type is UserRepository, phpstan should be able to works with it

To make it work, this extension (or the Symfony extension?) should:

  • Find classes that extends from ServiceEntityRepository
  • Get the entity type from constructor 2nd parameter
  • And do some magic ✨

In fact, I think it would be better if the repositories detection is part of Symfony extension. Since the Symfony extension can access the Symfony container, we can find services tagged with doctrine.repository_service and then use them:
Capture d’écran de 2019-07-10 06-37-23

What do you think?

Thanks! :)

GetRepository return value mismatch for ODM\GridFSDocument

I have this code:

$repository = $this->documentManager->getRepository(Document::class);
assert($repository instanceof \Doctrine\ODM\MongoDB\Repository\DefaultGridFSRepository);

The assert passes but I get this PhpStan error:

Instanceof between                                                   
Doctrine\ORM\EntityRepository<MyCompany\ODM\File\Document> and           
Doctrine\ODM\MongoDB\Repository\DefaultGridFSRepository will always  
evaluate to false. 

Note that the document for which I am getting a repository is not a standard document but rather GridFS file document which has some specifics.

The code snippet is taken from here.

Internal error: Class Doctrine\ORM\EntityRepository does not exist

I'd like to open new issue here, but it was discussed after closing this issue: #51

Looks like this plugin depends on the doctrine/orm although it definitely is useful for doctrine/odm as also stated in readme (Also supports Doctrine ODM.).

The fact is that it really works well for ODM project and I could delete a few ignore patterns. But I had to add doctrine/orm to my composer.json to make it work. Which is a shame.

I think it would not harm if this package added doctrine/orm as one of its own dependencies.

find return type not actually being set?

I'm probably misunderstanding the README for this plugin, but I have this:

namespace AquaSafeModel\Repository;

use Doctrine\ORM\EntityRepository as DoctrineEntityRepository;

abstract class EntityRepository extends DoctrineEntityRepository implements EntityRepositoryInterface
{

Then I have this

namespace AquaSafeModel\Repository;

use AquaSafeModel\Collection\EntityCollection;
use AquaSafeModel\Entity\Process;

class ProcessRepository extends EntityRepository
{

    /** @return Process */
    public function find($id, $lockMode = null, $lockVersion = null): ?Process
    {
        return parent::find($id);
    }

But I still get this from PHPStan:

  108    Call to an undefined method AquaSafeModel\Entity\BaseEntity::getCompetencies().  

I was expecting no output.

Of course, getCompetencies() exists on Process, not BaseEntity.

For reference, here's my phpstan.neon:

parameters:
    doctrine:
        repositoryClass: AquaSafeModel\Repository\EntityRepository
includes:
    - vendor/phpstan/phpstan-doctrine/extension.neon

I see in the README that "This extension does not yet support custom repositoryClass specified for each entity class." but I'm thinking that means in the ORM annotations, not my case of subclassing above...

Repository type does not match with ManagerRegistry

This PHP code part is valid:

$user = $this->getDoctrine()->getRepository(User::class)->find(
    $this->request->query->get('id')
);

$this->getDoctrine() return a ManagerRegistry instance.

But the final $user does not seems to be typed:

Call to an undefined method object::getUsername().

Using the EntityManagerInterface directly make things working.

Unable to resolve the template type

Since I updated to the latest version I'm getting this error (but strangely it's only at 1 place in my entire project):

Unable to resolve the template type T in call to method Doctrine\ORM\EntityManager::getRepository()

The line that triggers it:

$repository = $entityManager->getRepository(ApiKeyProjection::class);

issue with findOneByX

In some repositories I have some custom functions named like findOneByX

Example:

class ReviewRepository
{
    public function findOneByReviewConfigAndId(...) { }
}

Seems that is not working properly:

 ------ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 
  Line   someClass.php                                                                                                                                             
 ------ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 
  209    Call to method X\ReviewRepository<X\Entity\Review>::findOneByReviewConfigAndId() - entity X\Entity\Review does not have a field named  
         $reviewConfigAndId.                                                                                                                                                                         
 ------ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 

Support ResolveTargetEntityListener

We are using the ResolveTargetEntityListener of doctrine to allow using interfaces and it allows us to overwrite entities without using mapping superclass behaviour e.g.:

<many-to-one
    field="directory"
    target-entity="Sulu\Bundle\MyBundle\Model\DirectoryInterface"
    inversed-by="translations"
>
    <join-column name="directoryNo" referenced-column-name="no" on-delete="CASCADE" nullable="false"/>
</many-to-one>
    /**
     * @var DirectoryInterface
     */
    private $directory;

This ends currently with:

Sulu\Bundle\MyBundle\Model\DirectoryTranslation::$directory type mapping mismatch: property can contain Sulu\Bundle\MyBundle\Model\DirectoryInterface but database expects Sulu\Bundle\MyBundle\Model\Directory.

It would be awesome if it would take the Interface instead of the resolved entity so that phpstan would not error for resolved.

Missing DynamicReturnType for annotations Reader

There should be a DynamicReturnType for Doctrine\Common\Annotations\Reader. The getClassAnnotation, getMethodAnnotation and getPropertyAnnotation can only return the requested annotation or null.

Repository type not matched when set to a property

In a nutshell, this PHP class:

use Doctrine\ORM\Decorator\EntityManagerDecorator;
use PowerDNSBundle\Doctrine\ORM\PowerDNSEntityManager;
use PowerDNSBundle\Entity\PowerDNSDomain;

class DnsManager extends EntityManagerDecorator
{
    /**
     * @var ObjectRepository
     */
    private $domainRepository;

    public function __construct(PowerDNSEntityManager $wrapped)
    {
        parent::__construct($wrapped);

        $this->domainRepository = $this->getRepository(PowerDNSDomain::class);
    }

    public function addDomainIfNotExists($name, User $user, $ipV4 = null, $ipV6 = null, $checkDNS = true)
    {
        $nameCanonical = \idn_to_ascii($name);
        $domain = $this->domainRepository->findOneBy(['name' => $nameCanonical]);

        if (!$domain) {
            $domain = new PowerDNSDomain();
            $domain->setName($name);
            $domain->setOwnerId($user->getId());
            $this->saveDomain($domain, $ipV4, $ipV6, $checkDNS);
        }

        return $domain;
    }
}

Produces:

 ------ ---------------------------------------------------------------------- 
  Line   src/AppBundle/Manager/DnsManager.php                                  
 ------ ---------------------------------------------------------------------- 
  170    Method AppBundle\Manager\DnsManager::addDomainIfNotExists() should    
         return PowerDNSBundle\Entity\PowerDNSDomain but returns object.       

Replacing the line by $domain = $this->getRepository(PowerDNSDomain::class)->findOneBy(['name' => $nameCanonical]); directly "solve" the issue.

It seems the property assignation of repositories is not handled by the extension.

Internal error in Type/Doctrine/QueryBuilder/Expr/NewExprDynamicReturnTypeExtension

 ------ --------------------------------------------------------------
  Line   Doctrine/Expr/PartialSelect.php
 ------ --------------------------------------------------------------
         Internal error: Class 'parent' not found
         Run PHPStan with --debug option and post the stack trace to:
         https://github.com/phpstan/phpstan/issues/new
 ------ --------------------------------------------------------------
PHP Fatal error:  Uncaught Error: Class 'parent' not found in /.../vendor/phpstan/phpstan-doctrine/src/Type/Doctrine/QueryBuilder/Expr/NewExprDynamicReturnTypeExtension.php:52
Stack trace:
#0 /.../vendor/phpstan/phpstan/src/Analyser/Scope.php(1498): PHPStan\Type\Doctrine\QueryBuilder\Expr\NewExprDynamicReturnTypeExtension->getTypeFromStaticMethodCall(Object(PHPStan\Reflection\Php\PhpMethodReflection), Object(PhpParser\Node\Expr\StaticCall), Object(PHPStan\Analyser\Scope))
#1 /.../vendor/phpstan/phpstan/src/Analyser/Scope.php(372): PHPStan\Analyser\Scope->resolveType(Object(PhpParser\Node\Expr\StaticCall))
#2 /.../vendor/phpstan/phpstan/src/Rules/FunctionCallParametersCheck.php(98): PHPStan\Analyser\Scope->getType(Object(PhpParser\Node\Expr\StaticCall))
#3 /.../vendor/phpstan/phpstan/src/Rules/Methods/CallStaticMethodsRule.php(265): PHPStan\Rules\FunctionCallParamet in /.../vendor/phpstan/phpstan-doctrine/src/Type/Doctrine/QueryBuilder/Expr/NewExprDynamicReturnTypeExtension.php on line 52

EntityManager::getRepository should return the correct EntityRepository class

$em = $this->managerRegistry->getManager();

$travelDestinationRepository = $em->getRepository(TravelDestination::class);

$childDestinationIdsQueryBuilder = $travelDestinationRepository->getChildrenQueryBuilder($parentDestinationIds);

Line src/AppBundle/ApiPlatform/Doctrine/Orm/Filter/EventLocationFilter.php


99 Call to an undefined method
Doctrine\Common\Persistence\ObjectRepository::getChildrenQueryBuilder()
.


Method test::getItems() with return type void returns *NEVER* but should not return anything.

When the doctrine plugin is enabled, I get an error with when analysing this code in level 7:

<?php

use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Selectable;

class test
{
    /**
     * @var test[]&Collection&Selectable
     */
    private $items;

    /**
     * @return test[]&Collection&Selectable
     */
    public function getItems(): Collection
    {
        return $this->items;
    }
}

The error:

Method test::getItems() with return type void returns *NEVER* but should not return anything.

Avoid trying to resolve repository by interface

I have the following code:

/**
 * @param class-string<MyInterface> $entityClass
 */
public function test($entityClass)
{
    $entityRepository = $this->entityManager->getRepository($entityClass);
}

This will throw class_exists error as it tries to call class_exists on MyInterface.

Cannot satisfy not nullable OneToOne inverse side

I cannot find a way to satisfy the case

Property App\User::$stats type mapping mismatch: database can contain App\UserStats|null but property expects App\UserStats.

I have the following mapping :

<?php

class User
{

    /**
     * @var int
     *
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue
     */
    protected $id;

    /**
     * @var UserStats
     *
     * @ORM\OneToOne(targetEntity="App\UserStats", mappedBy="user")
     */
    protected $stats;

    // ... other properties

}

class UserStats
{
    
    /**
     * @var User
     *
     * @ORM\Id
     * @ORM\OneToOne(targetEntity="App\User", inversedBy="stats")
     */
    private $user;
    
    // ... other properties

}

If I add @ORM\JoinColumn(nullable=false) on User::$stats property then Doctrine is broken because JoinColumn annotation is expected on the owning side of the one to one relationship.

So I'm stuck with this case, because I can't explain to phpstan that my property isn't nullable :/

The setParameters method in AbstractQuery shows error on mixed type

This is for Doctrine ORM 2.7.0

In a Repository with the following:


$query = $em->createQuery('SELECT u FROM ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id');
$query->setParameters(array(
    'name' => 'Bob',
    'name2' => 'Alice',
    'id' => 321,
));
$users = $query->getResult();

According to the method, this should be allowed:

* @param ArrayCollection|mixed[] $parameters

But instead, the following error is generated:

Parameter #1 $parameters of method                                       
         Doctrine\ORM\AbstractQuery::setParameters() expects                      
         Doctrine\Common\Collections\ArrayCollection&iterable, array<string,      
         string> given.  

Support Symfony Doctrine Bridge

In Symfony's Doctrine bridge you 'must' inject Doctrine via the Symfony\Bridge\Doctrine\RegistryInterface:

    /** @var UserRepository */
    private $userRepository;

    public function __construct(RegistryInterface $registry)
    {
        $this->userRepository = $registry->getRepository(User::class);
    }

This code fails however with:

Property xxx::$userRepository (xxx\UserRepository) does not accept Doctrine\Common\Persistence\ObjectRepository.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.