Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nette/dibi driver support #462

Open
jakubvojacek opened this issue Nov 12, 2022 · 6 comments
Open

Nette/dibi driver support #462

jakubvojacek opened this issue Nov 12, 2022 · 6 comments

Comments

@jakubvojacek
Copy link
Contributor

Hello

I tried modifying phpstan-dba to add support to either https://github.com/dg/dibi or https://github.com/nette/database (both are very very similar in terms of methods, almost interchangeable).

The syntax is mainly

$connection = new \Dibi\Connection([...]);
// $connection->getDriver()->getResource() returns instance of \mysqli
$connectoin->query('SELECT * from users where users_name = ?', $username)->fetch();

i tried copying stuff from mysqli/pdo driver but they are different, they execute the query in 2 steps (prepare, execute) while dibi/nette does that in one method that accepts N-parameters.

Could you please guide me a bit how to add basic support for this?

Thanks a lot
Jakub

@staabm
Copy link
Owner

staabm commented Nov 12, 2022

Hi Jakub,

great to see people interessting in adding support for new db apis.

to add a new api to phpstan-dba you need 2 things:

  1. adding the query-string taking methods to the syntax error detection rule by adding full qualitfied method in

    class: staabm\PHPStanDba\Rules\SyntaxErrorInPreparedStatementMethodRule
    tags: [phpstan.rules.rule]
    arguments:
    classMethods:
    - 'Doctrine\DBAL\Connection::executeQuery'
    - 'Doctrine\DBAL\Connection::executeCacheQuery'
    - 'Doctrine\DBAL\Connection::executeStatement'
    - 'Doctrine\DBAL\Connection::fetchAssociative'
    - 'Doctrine\DBAL\Connection::fetchNumeric'
    - 'Doctrine\DBAL\Connection::fetchOne'
    - 'Doctrine\DBAL\Connection::fetchAllNumeric'
    - 'Doctrine\DBAL\Connection::fetchAllAssociative'
    - 'Doctrine\DBAL\Connection::fetchAllKeyValue'
    - 'Doctrine\DBAL\Connection::fetchAllAssociativeIndexed'
    - 'Doctrine\DBAL\Connection::fetchFirstColumn'
    - 'Doctrine\DBAL\Connection::iterateNumeric'
    - 'Doctrine\DBAL\Connection::iterateAssociative'
    - 'Doctrine\DBAL\Connection::iterateKeyValue'
    - 'Doctrine\DBAL\Connection::iterateAssociativeIndexed'
    - 'Doctrine\DBAL\Connection::iterateColumn'
    - 'Doctrine\DBAL\Connection::executeUpdate' # deprecated in doctrine
    -
    class: staabm\PHPStanDba\Rules\PdoStatementExecuteMethodRule
    tags: [phpstan.rules.rule]
    -
    class: staabm\PHPStanDba\Rules\SyntaxErrorInQueryMethodRule
    tags: [phpstan.rules.rule]
    arguments:
    classMethods:
    - 'PDO::query#0'
    - 'PDO::prepare#0'
    - 'mysqli::query#0'
    - 'mysqli::execute_query#0'
    - 'Doctrine\DBAL\Connection::query#0' # deprecated in doctrine
    - 'Doctrine\DBAL\Connection::exec#0' # deprecated in doctrine
    -
    class: staabm\PHPStanDba\Rules\SyntaxErrorInQueryFunctionRule
    tags: [phpstan.rules.rule]
    arguments:
    functionNames:
    - 'Deployer\runMysqlQuery#0'
    - 'mysqli_query#1'
    - 'mysqli_execute_query#1'
    -
    class: staabm\PHPStanDba\Rules\QueryPlanAnalyzerRule
    tags: [phpstan.rules.rule]
    arguments:
    classMethods:
    # prepared statement methods
    - 'Doctrine\DBAL\Connection::executeQuery#0'
    - 'Doctrine\DBAL\Connection::executeCacheQuery#0'
    - 'Doctrine\DBAL\Connection::executeStatement#0'
    - 'Doctrine\DBAL\Connection::fetchAssociative#0'
    - 'Doctrine\DBAL\Connection::fetchNumeric#0'
    - 'Doctrine\DBAL\Connection::fetchOne#0'
    - 'Doctrine\DBAL\Connection::fetchAllNumeric#0'
    - 'Doctrine\DBAL\Connection::fetchAllAssociative#0'
    - 'Doctrine\DBAL\Connection::fetchAllKeyValue#0'
    - 'Doctrine\DBAL\Connection::fetchAllAssociativeIndexed#0'
    - 'Doctrine\DBAL\Connection::fetchFirstColumn#0'
    - 'Doctrine\DBAL\Connection::iterateNumeric#0'
    - 'Doctrine\DBAL\Connection::iterateAssociative#0'
    - 'Doctrine\DBAL\Connection::iterateKeyValue#0'
    - 'Doctrine\DBAL\Connection::iterateAssociativeIndexed#0'
    - 'Doctrine\DBAL\Connection::iterateColumn#0'
    - 'Doctrine\DBAL\Connection::executeUpdate#0' # deprecated in doctrine
    # regular statements
    - 'PDO::query#0'
    - 'PDO::prepare#0'
    - 'mysqli::query#0'
    - 'mysqli::execute_query#0'
    - 'Doctrine\DBAL\Connection::query#0' # deprecated in doctrine
    - 'Doctrine\DBAL\Connection::exec#0' # deprecated in doctrine

  2. adding type inference via PHPStan ReturnTypeExtensions.
    you can take inspiration from already existing extensions. I am not at all familiar with any of the proposed apis, but I think they look similar to the already existing Doctrine DBAL stuff (which also separates the connection and the result object).
    see

I would suggest doing these 2 things in separate PRs. starting with 1) which is the easier step.

feel free to open a PR with your attemps, so I can give you more concrete hints

@jakubvojacek
Copy link
Contributor Author

Well 1) seems easy (I already kind of did that when I added those methods in services in the neon file) when I was testing, afaik the it would be this

🐞  jakubvojacek@~/Sites/phpstan-dba $ git diff
diff --git a/config/dba.neon b/config/dba.neon
index 2542a3d..250bdda 100644
--- a/config/dba.neon
+++ b/config/dba.neon
@@ -49,6 +49,7 @@ services:
                 - 'mysqli::execute_query#0'
                 - 'Doctrine\DBAL\Connection::query#0' # deprecated in doctrine
                 - 'Doctrine\DBAL\Connection::exec#0' # deprecated in doctrine
+                - 'Dibi\Connection::query#0'
 
     -
         class: staabm\PHPStanDba\Rules\SyntaxErrorInQueryFunctionRule
@@ -89,3 +90,4 @@ services:
                 - 'mysqli::execute_query#0'
                 - 'Doctrine\DBAL\Connection::query#0' # deprecated in doctrine
                 - 'Doctrine\DBAL\Connection::exec#0' # deprecated in doctrine
+                - 'Dibi\Connection::query#0'

but its hard for me to say that I didnt miss anything since I cannot test it properly without 2).

I will try looking at the samples that you sent and in case I manage somehow to make it work, I will open the PRs

Thanks again

@jakubvojacek
Copy link
Contributor Author

nevermind my previous post. The problem with Dibi\Connection::query is that it does not return any result, it requires another method to be called upon the query result

$connection->query('...')->fetchAll();
$connection->query('...')->fetchSingle();
$connection->query('...')->fetchAssoc('xxx');

and to write support for that does not seem as easy. But dibi also supports

$connection->fetchAll();
$connection->fetchSingle();
$connection->fetchAssoc();

which returns the desired results already. in that case 1) would be something like

🐞  jakubvojacek@~/Sites/phpstan-dba $ git diff
diff --git a/config/dba.neon b/config/dba.neon
index 2542a3d..f15bcae 100644
--- a/config/dba.neon
+++ b/config/dba.neon
@@ -49,6 +49,11 @@ services:
                 - 'mysqli::execute_query#0'
                 - 'Doctrine\DBAL\Connection::query#0' # deprecated in doctrine
                 - 'Doctrine\DBAL\Connection::exec#0' # deprecated in doctrine
+                - 'Dibi\Connection::fetch#0'
+                - 'Dibi\Connection::fetchSingle#0'
+                - 'Dibi\Connection::fetchAssoc#0'
+                - 'Dibi\Connection::fetchAll#0'
+                - 'Dibi\Connection::fetchPairs#0'
 
     -
         class: staabm\PHPStanDba\Rules\SyntaxErrorInQueryFunctionRule
@@ -89,3 +94,8 @@ services:
                 - 'mysqli::execute_query#0'
                 - 'Doctrine\DBAL\Connection::query#0' # deprecated in doctrine
                 - 'Doctrine\DBAL\Connection::exec#0' # deprecated in doctrine
+                - 'Dibi\Connection::fetch#0'
+                - 'Dibi\Connection::fetchSingle#0'
+                - 'Dibi\Connection::fetchAssoc#0'
+                - 'Dibi\Connection::fetchAll#0'
+                - 'Dibi\Connection::fetchPairs#0'
🐞  jakubvojacek@~/Sites/phpstan-dba $ 

and then I'd have to create rules for each of those methods, something like

final class DibiConnectionFetchDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
    public function getClass(): string
    {
        return \Dibi\Connection::class;
    }

    public function isMethodSupported(MethodReflection $methodReflection): bool
    {
        return \in_array(strtolower($methodReflection->getName()), ['fetch'], true);
    }

    public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
    {
        // some logic that will return the type of the single column (via something like inferType) that the user selected (nullable because the query might return zero rows)
    }
    
}

right?

Please excuse my "stupidness", I've never contributed to phpstan before so it might take some time before I'm up to speed.

@jakubvojacek
Copy link
Contributor Author

I am getting somewhat close to finishing stage two (will have to write some tests) but I got the feeling you won't like some parts of that :) but works like a charm so far

@staabm
Copy link
Owner

staabm commented Nov 15, 2022

I am getting somewhat close to finishing stage two

great news.

you won't like some parts of that :) but works like a charm so far

thats totally fine. I am looking forward to it.

@patrickkusebauch
Copy link
Contributor

Just a thought.

You could implement Dibi\Connection::query by templating Dibi\Result. Something like:

namespace Dibi

class Connection
{
  /**
   * @template args
   * @param args $args
   * @return Result<args, Dibi\Row::class>
   */
  public function query(mixed ...$args): Result {}

}

/**
 * @template args
 * @template rowType
 */
class Result
{
  /**
   * @return self<args, $class>
   */
  public function setRowClass(?string $class): self {}
}

Having the template on the Dibi\Result class with the arguments would allow you to get the template values in the DynamicMethodReturnTypeProvider to do the same analysis as is done on Dibi\Connection with the added benefit of setting the correct result class.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants