mirror of
https://github.com/danog/psalm-plugin-symfony.git
synced 2024-11-30 04:29:10 +01:00
[doctrine] query builder explicit parameter type for performance (#144)
This commit is contained in:
parent
9f5b7a1596
commit
cc89974d16
3
.github/workflows/integrate.yaml
vendored
3
.github/workflows/integrate.yaml
vendored
@ -25,7 +25,7 @@ jobs:
|
|||||||
dependencies:
|
dependencies:
|
||||||
- highest
|
- highest
|
||||||
- lowest
|
- lowest
|
||||||
|
|
||||||
exclude:
|
exclude:
|
||||||
- php-version: 7.3
|
- php-version: 7.3
|
||||||
dependencies: lowest
|
dependencies: lowest
|
||||||
@ -77,6 +77,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
php-version:
|
php-version:
|
||||||
- 7.1
|
- 7.1
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
<psalm
|
<psalm
|
||||||
errorLevel="1"
|
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns="https://getpsalm.org/schema/config"
|
xmlns="https://getpsalm.org/schema/config"
|
||||||
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
|
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
|
||||||
|
67
src/Handler/DoctrineQueryBuilderHandler.php
Normal file
67
src/Handler/DoctrineQueryBuilderHandler.php
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Psalm\SymfonyPsalmPlugin\Handler;
|
||||||
|
|
||||||
|
use PhpParser\Node\Expr;
|
||||||
|
use Psalm\Codebase;
|
||||||
|
use Psalm\CodeLocation;
|
||||||
|
use Psalm\Context;
|
||||||
|
use Psalm\IssueBuffer;
|
||||||
|
use Psalm\Plugin\Hook\AfterMethodCallAnalysisInterface;
|
||||||
|
use Psalm\StatementsSource;
|
||||||
|
use Psalm\SymfonyPsalmPlugin\Issue\QueryBuilderSetParameter;
|
||||||
|
use Psalm\Type\Union;
|
||||||
|
|
||||||
|
class DoctrineQueryBuilderHandler implements AfterMethodCallAnalysisInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function afterMethodCallAnalysis(
|
||||||
|
Expr $expr,
|
||||||
|
string $method_id,
|
||||||
|
string $appearing_method_id,
|
||||||
|
string $declaring_method_id,
|
||||||
|
Context $context,
|
||||||
|
StatementsSource $statements_source,
|
||||||
|
Codebase $codebase,
|
||||||
|
array &$file_replacements = [],
|
||||||
|
Union &$return_type_candidate = null
|
||||||
|
): void {
|
||||||
|
if ('Doctrine\ORM\QueryBuilder::setparameter' === $declaring_method_id) {
|
||||||
|
if (isset($expr->args[2])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$value = $expr->args[1]->value;
|
||||||
|
|
||||||
|
if (self::isValueObject($value, $context)) {
|
||||||
|
IssueBuffer::accepts(
|
||||||
|
new QueryBuilderSetParameter(new CodeLocation($statements_source, $value)),
|
||||||
|
$statements_source->getSuppressedIssues()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function isValueObject(Expr $value, Context $context): bool
|
||||||
|
{
|
||||||
|
if ($value instanceof Expr\Variable) {
|
||||||
|
$varName = $value->name;
|
||||||
|
if (is_string($varName)) {
|
||||||
|
$varName = '$'.$varName;
|
||||||
|
if ($context->hasVariable($varName)) {
|
||||||
|
$type = $context->vars_in_scope[$varName];
|
||||||
|
|
||||||
|
return $type->hasObjectType();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value instanceof Expr\New_) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
17
src/Issue/QueryBuilderSetParameter.php
Normal file
17
src/Issue/QueryBuilderSetParameter.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Psalm\SymfonyPsalmPlugin\Issue;
|
||||||
|
|
||||||
|
use Psalm\CodeLocation;
|
||||||
|
use Psalm\Issue\PluginIssue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see https://www.doctrine-project.org/projects/doctrine-orm/en/2.8/reference/query-builder.html#binding-parameters-to-your-query
|
||||||
|
*/
|
||||||
|
class QueryBuilderSetParameter extends PluginIssue
|
||||||
|
{
|
||||||
|
public function __construct(CodeLocation $code_location)
|
||||||
|
{
|
||||||
|
parent::__construct('To improve performance set explicit type for objects', $code_location);
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@ use Psalm\SymfonyPsalmPlugin\Handler\AnnotationHandler;
|
|||||||
use Psalm\SymfonyPsalmPlugin\Handler\ConsoleHandler;
|
use Psalm\SymfonyPsalmPlugin\Handler\ConsoleHandler;
|
||||||
use Psalm\SymfonyPsalmPlugin\Handler\ContainerDependencyHandler;
|
use Psalm\SymfonyPsalmPlugin\Handler\ContainerDependencyHandler;
|
||||||
use Psalm\SymfonyPsalmPlugin\Handler\ContainerHandler;
|
use Psalm\SymfonyPsalmPlugin\Handler\ContainerHandler;
|
||||||
|
use Psalm\SymfonyPsalmPlugin\Handler\DoctrineQueryBuilderHandler;
|
||||||
use Psalm\SymfonyPsalmPlugin\Handler\DoctrineRepositoryHandler;
|
use Psalm\SymfonyPsalmPlugin\Handler\DoctrineRepositoryHandler;
|
||||||
use Psalm\SymfonyPsalmPlugin\Handler\HeaderBagHandler;
|
use Psalm\SymfonyPsalmPlugin\Handler\HeaderBagHandler;
|
||||||
use Psalm\SymfonyPsalmPlugin\Handler\RequiredSetterHandler;
|
use Psalm\SymfonyPsalmPlugin\Handler\RequiredSetterHandler;
|
||||||
@ -56,12 +57,17 @@ class Plugin implements PluginEntryPointInterface
|
|||||||
require_once __DIR__.'/Handler/ConsoleHandler.php';
|
require_once __DIR__.'/Handler/ConsoleHandler.php';
|
||||||
require_once __DIR__.'/Handler/ContainerDependencyHandler.php';
|
require_once __DIR__.'/Handler/ContainerDependencyHandler.php';
|
||||||
require_once __DIR__.'/Handler/RequiredSetterHandler.php';
|
require_once __DIR__.'/Handler/RequiredSetterHandler.php';
|
||||||
|
require_once __DIR__.'/Handler/DoctrineQueryBuilderHandler.php';
|
||||||
|
|
||||||
$api->registerHooksFromClass(HeaderBagHandler::class);
|
$api->registerHooksFromClass(HeaderBagHandler::class);
|
||||||
$api->registerHooksFromClass(ConsoleHandler::class);
|
$api->registerHooksFromClass(ConsoleHandler::class);
|
||||||
$api->registerHooksFromClass(ContainerDependencyHandler::class);
|
$api->registerHooksFromClass(ContainerDependencyHandler::class);
|
||||||
$api->registerHooksFromClass(RequiredSetterHandler::class);
|
$api->registerHooksFromClass(RequiredSetterHandler::class);
|
||||||
|
|
||||||
|
if (class_exists(\Doctrine\ORM\QueryBuilder::class)) {
|
||||||
|
$api->registerHooksFromClass(DoctrineQueryBuilderHandler::class);
|
||||||
|
}
|
||||||
|
|
||||||
if (class_exists(AnnotationRegistry::class)) {
|
if (class_exists(AnnotationRegistry::class)) {
|
||||||
require_once __DIR__.'/Handler/DoctrineRepositoryHandler.php';
|
require_once __DIR__.'/Handler/DoctrineRepositoryHandler.php';
|
||||||
/** @psalm-suppress DeprecatedMethod */
|
/** @psalm-suppress DeprecatedMethod */
|
||||||
|
68
tests/acceptance/acceptance/DoctrineQueryBuilder.feature
Normal file
68
tests/acceptance/acceptance/DoctrineQueryBuilder.feature
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
@symfony-common
|
||||||
|
Feature: Doctrine QueryBuilder
|
||||||
|
|
||||||
|
Background:
|
||||||
|
Given I have the following config
|
||||||
|
"""
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<psalm>
|
||||||
|
<projectFiles>
|
||||||
|
<directory name="."/>
|
||||||
|
<ignoreFiles> <directory name="../../vendor"/> </ignoreFiles>
|
||||||
|
</projectFiles>
|
||||||
|
|
||||||
|
<plugins>
|
||||||
|
<pluginClass class="Psalm\SymfonyPsalmPlugin\Plugin"/>
|
||||||
|
</plugins>
|
||||||
|
</psalm>
|
||||||
|
"""
|
||||||
|
And I have the following code preamble
|
||||||
|
"""
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Doctrine\ORM\QueryBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @psalm-suppress InvalidReturnType
|
||||||
|
* @return QueryBuilder
|
||||||
|
*/
|
||||||
|
function qb() {}
|
||||||
|
"""
|
||||||
|
|
||||||
|
Scenario: No complaint about explicit type for non-objects
|
||||||
|
Given I have the following code
|
||||||
|
"""
|
||||||
|
$qb = qb();
|
||||||
|
$qb->setParameter('string', 'string');
|
||||||
|
$qb->setParameter('integer', 1);
|
||||||
|
$qb->setParameter('bool', true);
|
||||||
|
|
||||||
|
$string = 'string';
|
||||||
|
$int = 1;
|
||||||
|
$bool = false;
|
||||||
|
$qb->setParameter('string', $string);
|
||||||
|
$qb->setParameter('integer', $int);
|
||||||
|
$qb->setParameter('bool', $bool);
|
||||||
|
"""
|
||||||
|
When I run Psalm
|
||||||
|
Then I see no errors
|
||||||
|
|
||||||
|
Scenario: Complaint about not setting explicit type for objects
|
||||||
|
Given I have the following code
|
||||||
|
"""
|
||||||
|
$qb = qb();
|
||||||
|
|
||||||
|
$qb->setParameter('date', new \DateTimeImmutable());
|
||||||
|
|
||||||
|
$date = new \DateTimeImmutable();
|
||||||
|
$qb->setParameter('date', $date);
|
||||||
|
|
||||||
|
$qb->setParameter('qb', $qb);
|
||||||
|
"""
|
||||||
|
When I run Psalm
|
||||||
|
Then I see these errors
|
||||||
|
| Type | Message |
|
||||||
|
| QueryBuilderSetParameter | To improve performance set explicit type for objects |
|
||||||
|
| QueryBuilderSetParameter | To improve performance set explicit type for objects |
|
||||||
|
| QueryBuilderSetParameter | To improve performance set explicit type for objects |
|
||||||
|
And I see no other errors
|
Loading…
Reference in New Issue
Block a user