Skip to content

Commit 7470c42

Browse files
committed
feat: Replace fetch with fetchAssociative
Signed-off-by: Carl Schwan <carl.schwan@nextcloud.com>
1 parent 5481792 commit 7470c42

3 files changed

Lines changed: 181 additions & 0 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Nextcloud\Rector\Rector\ReplaceFetchAllMethodCallRector;
6+
use Nextcloud\Rector\Set\NextcloudSets;
7+
use Rector\Config\RectorConfig;
8+
9+
return static function (RectorConfig $rectorConfig): void {
10+
$rectorConfig->sets([NextcloudSets::NEXTCLOUD_27]);
11+
$rectorConfig->rule(ReplaceFetchAllMethodCallRector::class);
12+
};
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH
7+
* SPDX-FileContributor: Carl Schwan
8+
* SPDX-License-Identifier: AGPL-3.0-or-later
9+
*/
10+
11+
namespace Nextcloud\Rector\Rector;
12+
13+
use PHPStan\Type\ObjectType;
14+
use PhpParser\Node;
15+
use PhpParser\Node\Arg;
16+
use PhpParser\Node\Expr\ClassConstFetch;
17+
use PhpParser\Node\Expr\MethodCall;
18+
use PhpParser\Node\Identifier;
19+
use Rector\Rector\AbstractRector;
20+
use Symplify\RuleDocGenerator\ValueObject\CodeSample\CodeSample;
21+
use Symplify\RuleDocGenerator\ValueObject\RuleDefinition;
22+
23+
use function is_string;
24+
25+
final class ReplaceFetchAllMethodCallRector extends AbstractRector
26+
{
27+
public function getRuleDefinition(): RuleDefinition
28+
{
29+
return new RuleDefinition(
30+
'Change \OCP\DB\IResult->fetchAll() to ->fetchAllAssociative() and other replacements',
31+
[
32+
new CodeSample(
33+
<<<'CODE_SAMPLE'
34+
use OCP\DB\IResult;
35+
use OCP\DB\IConnection;
36+
use OCP\DB\IQueryBuilder;
37+
38+
class SomeClass
39+
{
40+
public function run(IConnection $connection)
41+
{
42+
$qb = $connection->getQueryBuilder();
43+
$result = $qb->exec();
44+
return $result->fetchAll();
45+
}
46+
}
47+
CODE_SAMPLE,
48+
<<<'CODE_SAMPLE'
49+
use OCP\DB\IResult;
50+
use OCP\DB\IConnection;
51+
use OCP\DB\IQueryBuilder;
52+
53+
class SomeClass
54+
{
55+
public function run(IConnection $connection)
56+
{
57+
$qb = $connection->getQueryBuilder();
58+
$result = $qb->exec();
59+
return $result->fetchAllAssociative();
60+
}
61+
}
62+
CODE_SAMPLE,
63+
),
64+
],
65+
);
66+
}
67+
68+
/**
69+
* @return array<class-string<Node>>
70+
*/
71+
public function getNodeTypes(): array
72+
{
73+
return [MethodCall::class];
74+
}
75+
76+
public function refactor(Node $node): ?Node
77+
{
78+
if (!($node instanceof MethodCall)) {
79+
return null;
80+
}
81+
if ($this->isObjectType($node->var, new ObjectType('OCP\DB\IResult'))) {
82+
return $this->refactorResultStatement($node);
83+
}
84+
85+
return null;
86+
}
87+
88+
private function refactorResultStatement(MethodCall $methodCall): ?MethodCall
89+
{
90+
if ($this->isName($methodCall->name, 'fetchColumn')) {
91+
$methodCall->name = new Identifier('fetchOne');
92+
93+
return $methodCall;
94+
}
95+
96+
if ($this->isName($methodCall->name, 'fetch')) {
97+
$args = $methodCall->getArgs();
98+
if ($args === []) {
99+
$methodCall->name = new Identifier('fetchAssociative');
100+
101+
return $methodCall;
102+
}
103+
104+
$firstArg = $args[0];
105+
106+
$newMethodName = $this->resolveFirstMethodName($firstArg, false);
107+
if (is_string($newMethodName)) {
108+
$methodCall->args = [];
109+
110+
$methodCall->name = new Identifier($newMethodName);
111+
112+
return $methodCall;
113+
}
114+
115+
return null;
116+
}
117+
118+
if ($this->isName($methodCall->name, 'fetchAll')) {
119+
$args = $methodCall->getArgs();
120+
if ($args === []) {
121+
$methodCall->name = new Identifier('fetchAllAssociative');
122+
123+
return $methodCall;
124+
}
125+
126+
$firstArg = $args[0];
127+
128+
$newMethodName = $this->resolveFirstMethodName($firstArg, true);
129+
if (is_string($newMethodName)) {
130+
$methodCall->args = [];
131+
132+
$methodCall->name = new Identifier($newMethodName);
133+
134+
return $methodCall;
135+
}
136+
}
137+
138+
return null;
139+
}
140+
141+
private function resolveFirstMethodName(Arg $firstArg, bool $all = false): ?string
142+
{
143+
if (!$firstArg->value instanceof ClassConstFetch) {
144+
return null;
145+
}
146+
147+
$classConstFetch = $firstArg->value;
148+
if (!$this->isName($classConstFetch->class, 'PDO')) {
149+
return null;
150+
}
151+
152+
if ($this->isName($classConstFetch->name, 'FETCH_COLUMN')) {
153+
return $all ? 'fetchFirstColumn' : 'fetchOne';
154+
}
155+
156+
if ($this->isName($classConstFetch->name, 'FETCH_ASSOC')) {
157+
return $all ? 'fetchAllAssociative' : 'fetchAssociative';
158+
}
159+
160+
if ($this->isName($classConstFetch->name, 'FETCH_NUM')) {
161+
return $all ? 'fetchAllNumeric' : 'fetchNumeric';
162+
}
163+
164+
return null;
165+
}
166+
}

src/Set/NextcloudSets.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,7 @@ final class NextcloudSets
1212
public const NEXTCLOUD_28 = self::NEXTCLOUD_27;
1313
public const NEXTCLOUD_29 = self::NEXTCLOUD_27;
1414
public const NEXTCLOUD_30 = self::NEXTCLOUD_27;
15+
public const NEXTCLOUD_31 = self::NEXTCLOUD_27;
16+
public const NEXTCLOUD_32 = self::NEXTCLOUD_27;
17+
public const NEXTCLOUD_33 = __DIR__ . '/../../config/nextcloud-33/nextcloud-33-deprecations.php';
1518
}

0 commit comments

Comments
 (0)