diff --git a/.travis.yml b/.travis.yml index 7f46b336..06f69511 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,7 @@ php: - 7.1 - 7.2 before_script: + - pecl install -f mongodb-stable - composer self-update - composer install script: diff --git a/composer.json b/composer.json index 6b438d95..e7991569 100644 --- a/composer.json +++ b/composer.json @@ -25,12 +25,15 @@ "slevomat/coding-standard": "^4.5.2", "doctrine/common": "^2.7", "doctrine/orm": "^2.5", - "doctrine/collections": "^1.0" + "doctrine/collections": "^1.0", + "doctrine/mongodb-odm": "^1.2", + "alcaeus/mongo-php-adapter": "^1.1" }, "conflict": { "doctrine/common": "<2.7", "doctrine/orm": "<2.5", - "doctrine/collections": "<1.0" + "doctrine/collections": "<1.0", + "doctrine/mongodb-odm": "<1.2" }, "autoload": { "psr-4": { @@ -39,5 +42,10 @@ }, "autoload-dev": { "classmap": ["tests/"] + }, + "config": { + "platform": { + "ext-mongo": "1.6.16" + } } } diff --git a/extension.neon b/extension.neon index 21e8e248..2d72010f 100644 --- a/extension.neon +++ b/extension.neon @@ -1,6 +1,6 @@ parameters: doctrine: - repositoryClass: Doctrine\ORM\EntityRepository + objectManagerLoader: ~ services: - @@ -12,13 +12,11 @@ services: tags: - phpstan.broker.dynamicMethodReturnTypeExtension - - class: PHPStan\Type\Doctrine\EntityManagerFindDynamicReturnTypeExtension + class: PHPStan\Type\Doctrine\ObjectManagerFindDynamicReturnTypeExtension tags: - phpstan.broker.dynamicMethodReturnTypeExtension - - class: PHPStan\Type\Doctrine\EntityManagerGetRepositoryDynamicReturnTypeExtension - arguments: - repositoryClass: %doctrine.repositoryClass% + class: PHPStan\Type\Doctrine\ObjectManagerGetRepositoryDynamicReturnTypeExtension tags: - phpstan.broker.dynamicMethodReturnTypeExtension - @@ -26,6 +24,10 @@ services: tags: - phpstan.broker.dynamicMethodReturnTypeExtension - - class: PHPStan\Type\Doctrine\EntityRepositoryDynamicReturnTypeExtension + class: PHPStan\Type\Doctrine\ObjectRepositoryDynamicReturnTypeExtension tags: - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: PHPStan\Type\Doctrine\ObjectMetadataResolver + arguments: + objectManagerLoader: %doctrine.objectManagerLoader% diff --git a/phpstan.neon b/phpstan.neon index 78c68c2d..d72de844 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,3 +2,7 @@ includes: - vendor/phpstan/phpstan-strict-rules/rules.neon - vendor/phpstan/phpstan-phpunit/extension.neon - vendor/phpstan/phpstan-phpunit/rules.neon + +parameters: + excludes_analyse: + - */tests/*/data/* diff --git a/src/Type/Doctrine/EntityManagerFindDynamicReturnTypeExtension.php b/src/Type/Doctrine/ObjectManagerFindDynamicReturnTypeExtension.php similarity index 82% rename from src/Type/Doctrine/EntityManagerFindDynamicReturnTypeExtension.php rename to src/Type/Doctrine/ObjectManagerFindDynamicReturnTypeExtension.php index 2498cc8d..f0c5fa26 100644 --- a/src/Type/Doctrine/EntityManagerFindDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/ObjectManagerFindDynamicReturnTypeExtension.php @@ -11,7 +11,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; -class EntityManagerFindDynamicReturnTypeExtension implements \PHPStan\Type\DynamicMethodReturnTypeExtension +class ObjectManagerFindDynamicReturnTypeExtension implements \PHPStan\Type\DynamicMethodReturnTypeExtension { public function getClass(): string @@ -43,12 +43,7 @@ public function getTypeFromMethodCall( return $mixedType; } - $type = new ObjectType($argType->getValue()); - if ($methodReflection->getName() === 'find') { - $type = TypeCombinator::addNull($type); - } - - return $type; + return TypeCombinator::addNull(new ObjectType($argType->getValue())); } } diff --git a/src/Type/Doctrine/EntityManagerGetRepositoryDynamicReturnTypeExtension.php b/src/Type/Doctrine/ObjectManagerGetRepositoryDynamicReturnTypeExtension.php similarity index 68% rename from src/Type/Doctrine/EntityManagerGetRepositoryDynamicReturnTypeExtension.php rename to src/Type/Doctrine/ObjectManagerGetRepositoryDynamicReturnTypeExtension.php index b383c18f..ce3dd371 100644 --- a/src/Type/Doctrine/EntityManagerGetRepositoryDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/ObjectManagerGetRepositoryDynamicReturnTypeExtension.php @@ -10,15 +10,15 @@ use PHPStan\Type\MixedType; use PHPStan\Type\Type; -class EntityManagerGetRepositoryDynamicReturnTypeExtension implements \PHPStan\Type\DynamicMethodReturnTypeExtension +class ObjectManagerGetRepositoryDynamicReturnTypeExtension implements \PHPStan\Type\DynamicMethodReturnTypeExtension { - /** @var string */ - private $repositoryClass; + /** @var ObjectMetadataResolver */ + private $metadataResolver; - public function __construct(string $repositoryClass) + public function __construct(ObjectMetadataResolver $metadataResolver) { - $this->repositoryClass = $repositoryClass; + $this->metadataResolver = $metadataResolver; } public function getClass(): string @@ -47,7 +47,14 @@ public function getTypeFromMethodCall( return new MixedType(); } - return new EntityRepositoryType($argType->getValue(), $this->repositoryClass); + $objectName = $argType->getValue(); + $repositoryClass = $this->metadataResolver->getRepositoryClass($objectName); + + if ($repositoryClass === null) { + return new MixedType(); + } + + return new ObjectRepositoryType($objectName, $repositoryClass); } } diff --git a/src/Type/Doctrine/ObjectMetadataResolver.php b/src/Type/Doctrine/ObjectMetadataResolver.php new file mode 100644 index 00000000..23ff2dc3 --- /dev/null +++ b/src/Type/Doctrine/ObjectMetadataResolver.php @@ -0,0 +1,47 @@ +objectManager = $this->getObjectManager($objectManagerLoader); + } + + private function getObjectManager(string $objectManagerLoader): ObjectManager + { + if (! file_exists($objectManagerLoader) && ! is_readable($objectManagerLoader)) { + throw new RuntimeException('Object manager could not be loaded'); + } + + return require $objectManagerLoader; + } + + public function getRepositoryClass(string $className): ?string + { + $metatada = $this->objectManager->getClassMetadata($className); + + if ($metatada instanceof ORMMetadata) { + return $metatada->customRepositoryClassName ?? 'Doctrine\ORM\EntityRepository'; + } + + if ($metatada instanceof ODMMetadata) { + return $metatada->customRepositoryClassName ?? 'Doctrine\ODM\MongoDB\DocumentRepository'; + } + + return null; + } + +} diff --git a/src/Type/Doctrine/EntityRepositoryDynamicReturnTypeExtension.php b/src/Type/Doctrine/ObjectRepositoryDynamicReturnTypeExtension.php similarity index 87% rename from src/Type/Doctrine/EntityRepositoryDynamicReturnTypeExtension.php rename to src/Type/Doctrine/ObjectRepositoryDynamicReturnTypeExtension.php index b542508c..be363648 100644 --- a/src/Type/Doctrine/EntityRepositoryDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/ObjectRepositoryDynamicReturnTypeExtension.php @@ -12,12 +12,12 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; -class EntityRepositoryDynamicReturnTypeExtension implements \PHPStan\Type\DynamicMethodReturnTypeExtension +class ObjectRepositoryDynamicReturnTypeExtension implements \PHPStan\Type\DynamicMethodReturnTypeExtension { public function getClass(): string { - return 'Doctrine\ORM\EntityRepository'; + return 'Doctrine\Common\Persistence\ObjectRepository'; } public function isMethodSupported(MethodReflection $methodReflection): bool @@ -36,7 +36,7 @@ public function getTypeFromMethodCall( ): Type { $calledOnType = $scope->getType($methodCall->var); - if (!$calledOnType instanceof EntityRepositoryType) { + if (!$calledOnType instanceof ObjectRepositoryType) { return new MixedType(); } $methodName = $methodReflection->getName(); diff --git a/src/Type/Doctrine/EntityRepositoryType.php b/src/Type/Doctrine/ObjectRepositoryType.php similarity index 92% rename from src/Type/Doctrine/EntityRepositoryType.php rename to src/Type/Doctrine/ObjectRepositoryType.php index 49c8c510..f5538b35 100644 --- a/src/Type/Doctrine/EntityRepositoryType.php +++ b/src/Type/Doctrine/ObjectRepositoryType.php @@ -5,7 +5,7 @@ use PHPStan\Type\ObjectType; use PHPStan\Type\VerbosityLevel; -class EntityRepositoryType extends ObjectType +class ObjectRepositoryType extends ObjectType { /** @var string */ diff --git a/tests/DoctrineIntegration/ODM/DocumentManagerIntegrationTest.php b/tests/DoctrineIntegration/ODM/DocumentManagerIntegrationTest.php new file mode 100644 index 00000000..fccbceb4 --- /dev/null +++ b/tests/DoctrineIntegration/ODM/DocumentManagerIntegrationTest.php @@ -0,0 +1,38 @@ +repository = $documentManager->getRepository(MyDocument::class); + } + + public function get(): void + { + $test = $this->repository->get('testing'); + $test->doSomethingElse(); + } +} + +/** + * @Document(repositoryClass=MyRepository::class) + */ +class MyDocument +{ + /** + * @Id(strategy="NONE", type="string") + * + * @var string + */ + private $id; + + public function doSomethingElse(): void + { + } +} + +class MyRepository extends DocumentRepository +{ + public function get(string $id): MyDocument + { + $document = $this->find($id); + + if ($document === null) { + throw new RuntimeException('Not found...'); + } + + return $document; + } +} diff --git a/tests/DoctrineIntegration/ODM/data/documentManagerDynamicReturn.php b/tests/DoctrineIntegration/ODM/data/documentManagerDynamicReturn.php new file mode 100644 index 00000000..7acbb0a7 --- /dev/null +++ b/tests/DoctrineIntegration/ODM/data/documentManagerDynamicReturn.php @@ -0,0 +1,71 @@ +documentManager = $documentManager; + } + + public function findDynamicType(): void + { + $test = $this->documentManager->find(MyDocument::class, 'blah-123'); + + if ($test === null) { + throw new RuntimeException('Sorry, but no...'); + } + + $test->doSomething(); + } + + public function getReferenceDynamicType(): void + { + $test = $this->documentManager->getReference(MyDocument::class, 'blah-123'); + + if ($test === null) { + throw new RuntimeException('Sorry, but no...'); + } + + $test->doSomething(); + } + + public function getPartialReferenceDynamicType(): void + { + $test = $this->documentManager->getPartialReference(MyDocument::class, 'blah-123'); + + if ($test === null) { + throw new RuntimeException('Sorry, but no...'); + } + + $test->doSomething(); + } +} + +/** + * @Document() + */ +class MyDocument +{ + /** + * @Id(strategy="NONE", type="string") + * + * @var string + */ + private $id; + + public function doSomething(): void + { + } +} diff --git a/tests/DoctrineIntegration/ODM/data/documentManagerMergeReturn.php b/tests/DoctrineIntegration/ODM/data/documentManagerMergeReturn.php new file mode 100644 index 00000000..4a190fe1 --- /dev/null +++ b/tests/DoctrineIntegration/ODM/data/documentManagerMergeReturn.php @@ -0,0 +1,43 @@ +documentManager = $documentManager; + } + + public function merge(): void + { + $test = $this->documentManager->merge(new MyDocument()); + $test->doSomething(); + } +} + +/** + * @Document() + */ +class MyDocument +{ + /** + * @Id(strategy="NONE", type="string") + * + * @var string + */ + private $id; + + public function doSomething(): void + { + } +} diff --git a/tests/DoctrineIntegration/ODM/data/documentRepositoryDynamicReturn.php b/tests/DoctrineIntegration/ODM/data/documentRepositoryDynamicReturn.php new file mode 100644 index 00000000..590bdedc --- /dev/null +++ b/tests/DoctrineIntegration/ODM/data/documentRepositoryDynamicReturn.php @@ -0,0 +1,79 @@ +repository = $documentManager->getRepository(MyDocument::class); + } + + public function findDynamicType(): void + { + $test = $this->repository->find(1); + + if ($test === null) { + throw new RuntimeException('Sorry, but no...'); + } + + $test->doSomething(); + } + + public function findOneByDynamicType(): void + { + $test = $this->repository->findOneBy(['blah' => 'testing']); + + if ($test === null) { + throw new RuntimeException('Sorry, but no...'); + } + + $test->doSomething(); + } + + public function findAllDynamicType(): void + { + $items = $this->repository->findAll(); + + foreach ($items as $test) { + $test->doSomething(); + } + } + + public function findByDynamicType(): void + { + $items = $this->repository->findBy(['blah' => 'testing']); + + foreach ($items as $test) { + $test->doSomething(); + } + } +} + +/** + * @Document() + */ +class MyDocument +{ + /** + * @Id(strategy="NONE", type="string") + * + * @var string + */ + private $id; + + public function doSomething(): void + { + } +} diff --git a/tests/DoctrineIntegration/ODM/document-manager.php b/tests/DoctrineIntegration/ODM/document-manager.php new file mode 100644 index 00000000..403a6ab6 --- /dev/null +++ b/tests/DoctrineIntegration/ODM/document-manager.php @@ -0,0 +1,29 @@ +setProxyDir(__DIR__); +$config->setProxyNamespace('PHPstan\Doctrine\OdmProxies'); +$config->setMetadataCacheImpl(new ArrayCache()); +$config->setHydratorDir(__DIR__); +$config->setHydratorNamespace('PHPstan\Doctrine\OdmHydrators'); + +$config->setMetadataDriverImpl( + new AnnotationDriver( + new AnnotationReader(), + [__DIR__ . '/data'] + ) +); + +return DocumentManager::create( + null, + $config +); diff --git a/tests/DoctrineIntegration/ODM/phpstan.neon b/tests/DoctrineIntegration/ODM/phpstan.neon new file mode 100644 index 00000000..c83c9fb1 --- /dev/null +++ b/tests/DoctrineIntegration/ODM/phpstan.neon @@ -0,0 +1,6 @@ +includes: + - ../../../extension.neon + +parameters: + doctrine: + objectManagerLoader: tests/DoctrineIntegration/ODM/document-manager.php diff --git a/tests/DoctrineIntegration/ORM/EntityManagerIntegrationTest.php b/tests/DoctrineIntegration/ORM/EntityManagerIntegrationTest.php new file mode 100644 index 00000000..ab226b07 --- /dev/null +++ b/tests/DoctrineIntegration/ORM/EntityManagerIntegrationTest.php @@ -0,0 +1,38 @@ +repository = $entityManager->getRepository(MyEntity::class); + } + + public function get(): void + { + $test = $this->repository->get(1); + $test->doSomethingElse(); + } +} + +/** + * @ORM\Entity(repositoryClass=MyRepository::class) + */ +class MyEntity +{ + /** + * @ORM\Id() + * @ORM\GeneratedValue() + * @ORM\Column(type="integer") + * + * @var int + */ + private $id; + + public function doSomethingElse(): void + { + } +} + +class MyRepository extends EntityRepository +{ + public function get(int $id): MyEntity + { + $entity = $this->find($id); + + if ($entity === null) { + throw new RuntimeException('Not found...'); + } + + return $entity; + } +} diff --git a/tests/DoctrineIntegration/ORM/data/entityManagerDynamicReturn.php b/tests/DoctrineIntegration/ORM/data/entityManagerDynamicReturn.php new file mode 100644 index 00000000..0f18ec85 --- /dev/null +++ b/tests/DoctrineIntegration/ORM/data/entityManagerDynamicReturn.php @@ -0,0 +1,72 @@ +entityManager = $entityManager; + } + + public function findDynamicType(): void + { + $test = $this->entityManager->find(MyEntity::class, 1); + + if ($test === null) { + throw new RuntimeException('Sorry, but no...'); + } + + $test->doSomething(); + } + + public function getReferenceDynamicType(): void + { + $test = $this->entityManager->getReference(MyEntity::class, 1); + + if ($test === null) { + throw new RuntimeException('Sorry, but no...'); + } + + $test->doSomething(); + } + + public function getPartialReferenceDynamicType(): void + { + $test = $this->entityManager->getPartialReference(MyEntity::class, 1); + + if ($test === null) { + throw new RuntimeException('Sorry, but no...'); + } + + $test->doSomething(); + } +} + +/** + * @ORM\Entity() + */ +class MyEntity +{ + /** + * @ORM\Id() + * @ORM\GeneratedValue() + * @ORM\Column(type="integer") + * + * @var int + */ + private $id; + + public function doSomething(): void + { + } +} diff --git a/tests/DoctrineIntegration/ORM/data/entityManagerMergeReturn.php b/tests/DoctrineIntegration/ORM/data/entityManagerMergeReturn.php new file mode 100644 index 00000000..b72a5df2 --- /dev/null +++ b/tests/DoctrineIntegration/ORM/data/entityManagerMergeReturn.php @@ -0,0 +1,44 @@ +entityManager = $entityManager; + } + + public function merge(): void + { + $test = $this->entityManager->merge(new MyEntity()); + $test->doSomething(); + } +} + +/** + * @ORM\Entity() + */ +class MyEntity +{ + /** + * @ORM\Id() + * @ORM\GeneratedValue() + * @ORM\Column(type="integer") + * + * @var int + */ + private $id; + + public function doSomething(): void + { + } +} diff --git a/tests/DoctrineIntegration/ORM/data/entityRepositoryDynamicReturn.php b/tests/DoctrineIntegration/ORM/data/entityRepositoryDynamicReturn.php new file mode 100644 index 00000000..d26b037d --- /dev/null +++ b/tests/DoctrineIntegration/ORM/data/entityRepositoryDynamicReturn.php @@ -0,0 +1,80 @@ +repository = $entityManager->getRepository(MyEntity::class); + } + + public function findDynamicType(): void + { + $test = $this->repository->find(1); + + if ($test === null) { + throw new RuntimeException('Sorry, but no...'); + } + + $test->doSomething(); + } + + public function findOneByDynamicType(): void + { + $test = $this->repository->findOneBy(['blah' => 'testing']); + + if ($test === null) { + throw new RuntimeException('Sorry, but no...'); + } + + $test->doSomething(); + } + + public function findAllDynamicType(): void + { + $items = $this->repository->findAll(); + + foreach ($items as $test) { + $test->doSomething(); + } + } + + public function findByDynamicType(): void + { + $items = $this->repository->findBy(['blah' => 'testing']); + + foreach ($items as $test) { + $test->doSomething(); + } + } +} + +/** + * @ORM\Entity() + */ +class MyEntity +{ + /** + * @ORM\Id() + * @ORM\GeneratedValue() + * @ORM\Column(type="integer") + * + * @var int + */ + private $id; + + public function doSomething(): void + { + } +} diff --git a/tests/DoctrineIntegration/ORM/entity-manager.php b/tests/DoctrineIntegration/ORM/entity-manager.php new file mode 100644 index 00000000..45e13b3a --- /dev/null +++ b/tests/DoctrineIntegration/ORM/entity-manager.php @@ -0,0 +1,30 @@ +setProxyDir(__DIR__); +$config->setProxyNamespace('PHPstan\Doctrine\OrmProxies'); +$config->setMetadataCacheImpl(new ArrayCache()); + +$config->setMetadataDriverImpl( + new AnnotationDriver( + new AnnotationReader(), + [__DIR__ . '/data'] + ) +); + +return EntityManager::create( + [ + 'driver' => 'pdo_sqlite', + 'memory' => true, + ], + $config +); diff --git a/tests/DoctrineIntegration/ORM/phpstan.neon b/tests/DoctrineIntegration/ORM/phpstan.neon new file mode 100644 index 00000000..80883c95 --- /dev/null +++ b/tests/DoctrineIntegration/ORM/phpstan.neon @@ -0,0 +1,6 @@ +includes: + - ../../../extension.neon + +parameters: + doctrine: + objectManagerLoader: tests/DoctrineIntegration/ORM/entity-manager.php