Completed
Push — master ( 90100d...399d17 )
by MusikAnimal
02:24
created

EditSummaryController::indexAction()   C

Complexity

Conditions 12
Paths 48

Size

Total Lines 50
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 50
rs 5.3904
c 0
b 0
f 0
cc 12
eloc 29
nc 48
nop 2

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * This file contains only the SimpleEditCounterController class.
4
 */
5
6
namespace AppBundle\Controller;
7
8
use Doctrine\DBAL\Connection;
9
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
10
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
11
use Symfony\Component\HttpFoundation\Request;
12
use Symfony\Component\HttpFoundation\Response;
13
use Psr\Cache\CacheItemPoolInterface;
14
use Xtools\Project;
15
use Xtools\ProjectRepository;
16
use Xtools\User;
17
use DateTime;
18
use DateInterval;
19
20
/**
21
 * This controller handles the Simple Edit Counter tool.
22
 */
23
class EditSummaryController extends Controller
24
{
25
26
    /**
27
     * Get the tool's shortname.
28
     * @return string
29
     */
30
    public function getToolShortname()
31
    {
32
        return 'es';
33
    }
34
35
    /**
36
     * The Edit Summary search form.
37
     *
38
     * @param Request $request The HTTP request.
39
     * @param string  $project The project database name or domain.
40
     *
41
     * @Route("/editsummary",           name="es")
42
     * @Route("/editsummary",           name="EditSummary")
43
     * @Route("/editsummary/",          name="EditSummarySlash")
44
     * @Route("/editsummary/index.php", name="EditSummaryIndexPhp")
45
     * @Route("/editsummary/{project}", name="EditSummaryProject")
46
     *
47
     * @return Response
48
     */
49
    public function indexAction(Request $request, $project = null)
50
    {
51
        // Get the query parameters.
52
        $projectName = $project ?: $request->query->get('project');
53
        $username = $request->query->get('username');
54
        $namespace = $request->query->get('namespace');
55
56
        // Default to mainspace.
57
        if (empty($namespace)) {
58
            $namespace = '0';
59
        }
60
61
        // Legacy XTools.
62
        $user = $request->query->get('name');
63
        if (empty($username) && isset($user)) {
64
            $username = $user;
65
        }
66
        $wiki = $request->query->get('wiki');
67
        $lang = $request->query->get('lang');
68
        if (isset($wiki) && isset($lang) && empty($project)) {
69
            $projectName = $lang.'.'.$wiki.'.org';
70
        }
71
72
        // If we've got a project, user, and namespace, redirect to results.
73
        if ($projectName != '' && $username != '' && $namespace != '') {
74
            $routeParams = [
75
                'project' => $projectName,
76
                'username' => $username,
77
                'namespace' => $namespace,
78
            ];
79
            return $this->redirectToRoute('EditSummaryResult', $routeParams);
80
        }
81
82
        // Instantiate the project if we can, or use the default.
83
        $theProject = (!empty($projectName))
84
            ? ProjectRepository::getProject($projectName, $this->container)
85
            : ProjectRepository::getDefaultProject($this->container);
86
87
        // Show the form.
88
        return $this->render(
89
            'editSummary/index.html.twig',
90
            [
91
                'xtPageTitle' => 'tool-es',
92
                'xtSubtitle' => 'tool-es-desc',
93
                'xtPage' => 'es',
94
                'project' => $theProject,
95
                'namespace' => (int) $namespace,
96
            ]
97
        );
98
    }
99
100
    /**
101
     * Display the Edit Summary results
102
     *
103
     * @param string $project  The project domain name.
104
     * @param string $username The username.
105
     * @param string $namespace Namespace ID or 'all' for all namespaces.
106
     *
107
     * @Route("/editsummary/{project}/{username}/{namespace}", name="EditSummaryResult")
108
     *
109
     * @return Response
110
     */
111
    public function resultAction($project, $username, $namespace = 0)
112
    {
113
        /**
114
         * Project object representing the project
115
         * @var Project $projectData
116
         */
117
        $projectData = ProjectRepository::getProject($project, $this->container);
118
119
        // Start by checking if the project exits.
120
        // If not, show a message and redirect
121
        if (!$projectData->exists()) {
122
            $this->addFlash('notice', ['invalid-project', $projectData]);
0 ignored issues
show
Documentation introduced by
array('invalid-project', $projectData) is of type array<integer,string|obj...ect<Xtools\\Project>"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
123
            return $this->redirectToRoute('EditSummary');
124
        }
125
126
        $user = new User($username);
127
128
        $editSummaryUsage = $this->getEditSummaryUsage($projectData, $user, $namespace);
129
130
        // If they have no edits, we have nothing to show
131
        if ($editSummaryUsage['totalEdits'] === 0) {
132
            $this->addFlash('notice', ['no-contribs']);
0 ignored issues
show
Documentation introduced by
array('no-contribs') is of type array<integer,string,{"0":"string"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
133
            return $this->redirectToRoute('EditSummary');
134
        }
135
136
        // Assign the values and display the template
137
        return $this->render(
138
            'editSummary/result.html.twig',
139
            array_merge($editSummaryUsage, [
140
                'xtPage' => 'es',
141
                'xtTitle' => $user->getUsername(),
142
                'user' => $user,
143
                'project' => $projectData,
144
                'namespace' => $namespace,
145
            ])
146
        );
147
    }
148
149
    /**
150
     * Get data on edit summary usage of the given user
151
     * @param  Project $project
152
     * @param  User $user
153
     * @param  string $namespace
154
     * @return array
155
     * @todo Should we move this to an actual Repository? Very specific to this controller
156
     */
157
    private function getEditSummaryUsage($project, $user, $namespace)
158
    {
159
        $dbName = $project->getDatabaseName();
160
161
        $cacheKey = 'editsummaryusage.' . $dbName . '.'
162
            . $user->getCacheKey() . '.' . $namespace;
163
164
        $cache = $this->container->get('cache.app');
165
        if ($cache->hasItem($cacheKey)) {
166
            return $cache->getItem($cacheKey)->get();
167
        }
168
169
        // Load the database tables
170
        $revisionTable = $project->getRepository()->getTableName($dbName, 'revision');
171
        $pageTable = $project->getRepository()->getTableName($dbName, 'page');
172
173
        /**
174
         * Connection to the replica database
175
         *
176
         * @var Connection $conn
177
         */
178
        $conn = $this->get('doctrine')->getManager('replicas')->getConnection();
179
180
        $condNamespace = $namespace === 'all' ? '' : 'AND page_namespace = :namespace';
181
        $pageJoin = $namespace === 'all' ? '' : "JOIN $pageTable ON rev_page = page_id";
182
        $username = $user->getUsername();
183
184
        // Prepare the query and execute
185
        $sql = "SELECT rev_comment, rev_timestamp, rev_minor_edit
186
                FROM  $revisionTable
187
    ​            $pageJoin
188
                WHERE rev_user_text = :username
189
                $condNamespace
190
                ORDER BY rev_timestamp DESC";
191
192
        $resultQuery = $conn->prepare($sql);
193
        $resultQuery->bindParam('username', $username);
194
        if ($namespace !== 'all') {
195
            $resultQuery->bindParam('namespace', $namespace);
196
        }
197
        $resultQuery->execute();
198
199 View Code Duplication
        if ($resultQuery->errorCode() > 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
200
            $this->addFlash('notice', ['no-result', $username]);
0 ignored issues
show
Documentation introduced by
array('no-result', $username) is of type array<integer,*,{"0":"string","1":"*"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
201
            return $this->redirectToRoute(
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->redirectTo...project->getDomain())); (Symfony\Component\HttpFoundation\RedirectResponse) is incompatible with the return type documented by AppBundle\Controller\Edi...er::getEditSummaryUsage of type array.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
202
                'EditSummaryProject',
203
                [
204
                    'project' => $project->getDomain()
205
                ]
206
            );
207
        }
208
209
        // Set defaults, so we don't get variable undefined errors
210
        $totalSummariesMajor = 0;
211
        $totalSummariesMinor = 0;
212
        $totalEditsMajor = 0;
213
        $totalEditsMinor = 0;
214
        $recentEditsMajor = 0;
215
        $recentEditsMinor = 0;
216
        $recentSummariesMajor = 0;
217
        $recentSummariesMinor = 0;
218
        $monthTotals = [];
219
        $monthEditsummaryTotals = [];
220
        $totalEdits = 0;
221
        $totalSummaries = 0;
222
223
        while ($row = $resultQuery->fetch()) {
224
            // Extract the date out of the date field
225
            $timestamp = DateTime::createFromFormat('YmdHis', $row['rev_timestamp']);
226
227
            $monthkey = date_format($timestamp, 'Y-m');
228
229
            // Check and see if the month is set for all major edits edits.
230
            // If not, default it to 1.
231
            if (!isset($monthTotals[$monthkey])) {
232
                $monthTotals[$monthkey] = 1;
233
            } else {
234
                $monthTotals[$monthkey]++;
235
            }
236
237
            // Grand total for number of edits
238
            $totalEdits++;
239
240
            // Total edit summaries
241
            if ($row['rev_comment'] !== '') {
242
                $totalSummaries++;
243
            }
244
245
            // Now do the same, if we have an edit summary
246
            if ($row['rev_minor_edit'] == 0) {
247 View Code Duplication
                if ($row['rev_comment'] !== '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
248
                    isset($monthEditsummaryTotals[$monthkey]) ?
249
                        $monthEditsummaryTotals[$monthkey]++ :
250
                        $monthEditsummaryTotals[$monthkey] = 1;
251
                    $totalSummariesMajor++;
252
                }
253
254
                // Now do the same for recent edits
255
                $totalEditsMajor++;
256
                if ($recentEditsMajor < 150) {
257
                    $recentEditsMajor++;
258
                    if ($row['rev_comment'] != '') {
259
                        $recentSummariesMajor++;
260
                    }
261
                }
262
            } else {
263
                // The exact same procedure as documented above for minor edits
264
                // If there is a comment, count it
265 View Code Duplication
                if ($row['rev_comment'] !== '') {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
266
                    isset($monthEditsummaryTotals[$monthkey]) ?
267
                        $monthEditsummaryTotals[$monthkey]++ :
268
                        $monthEditsummaryTotals[$monthkey] = 1;
269
                    $totalSummariesMinor++;
270
                    $totalEditsMinor++;
271
                } else {
272
                    $totalEditsMinor++;
273
                }
274
275
                // Handle recent edits
276
                if ($recentEditsMinor < 150) {
277
                    $recentEditsMinor++;
278
                    if ($row['rev_comment'] != '') {
279
                        $recentSummariesMinor++;
280
                    }
281
                }
282
            }
283
        }
284
285
        $result = [
286
            'totalEdits' => $totalEdits,
287
            'totalEditsMajor' => $totalEditsMajor,
288
            'totalEditsMinor' => $totalEditsMinor,
289
            'totalSummaries' => $totalSummaries,
290
            'totalSummariesMajor' => $totalSummariesMajor,
291
            'totalSummariesMinor' => $totalSummariesMinor,
292
            'recentEditsMajor' => $recentEditsMajor,
293
            'recentEditsMinor' => $recentEditsMinor,
294
            'recentSummariesMajor' => $recentSummariesMajor,
295
            'recentSummariesMinor' => $recentSummariesMinor,
296
            'monthTotals' => $monthTotals,
297
            'monthEditSumTotals' => $monthEditsummaryTotals,
298
        ];
299
300
        // Cache for 10 minutes, and return.
301
        $cacheItem = $cache->getItem($cacheKey)
302
            ->set($result)
303
            ->expiresAfter(new DateInterval('PT10M'));
304
        $cache->save($cacheItem);
305
306
        return $result;
307
    }
308
}
309