Completed
Pull Request — master (#111)
by MusikAnimal
02:25
created

EditSummaryController::getEditSummaryUsage()   F

Complexity

Conditions 18
Paths 593

Size

Total Lines 151
Code Lines 91

Duplication

Lines 24
Ratio 15.89 %

Importance

Changes 0
Metric Value
dl 24
loc 151
rs 2.3988
c 0
b 0
f 0
cc 18
eloc 91
nc 593
nop 3

How to fix   Long Method    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 XtoolsController
24
{
25
26
    /**
27
     * Get the tool's shortname.
28
     * @return string
29
     * @codeCoverageIgnore
30
     */
31
    public function getToolShortname()
32
    {
33
        return 'es';
34
    }
35
36
    /**
37
     * The Edit Summary search form.
38
     *
39
     * @param Request $request The HTTP request.
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)
50
    {
51
        $params = $this->parseQueryParams($request);
52
53
        // If we've got a project, user, and namespace, redirect to results.
54
        if (isset($params['project']) && isset($params['username']) && isset($params['namespace'])) {
55
            return $this->redirectToRoute('EditSummaryResult', $params);
56
        }
57
58
        // Convert the given project (or default project) into a Project instance.
59
        $params['project'] = $this->getProjectFromQuery($params);
60
61
        // Show the form.
62
        return $this->render('editSummary/index.html.twig', array_merge([
63
            'xtPageTitle' => 'tool-es',
64
            'xtSubtitle' => 'tool-es-desc',
65
            'xtPage' => 'es',
66
67
            // Defaults that will get overriden if in $params.
68
            'namespace' => 0,
69
        ], $params));
70
    }
71
72
    /**
73
     * Display the Edit Summary results
74
     *
75
     * @param Request $request The HTTP request.
76
     * @param string $namespace Namespace ID or 'all' for all namespaces.
77
     *
78
     * @Route("/editsummary/{project}/{username}/{namespace}", name="EditSummaryResult")
79
     *
80
     * @return Response
81
     * @codeCoverageIgnore
82
     */
83
    public function resultAction(Request $request, $namespace = 0)
84
    {
85
        $ret = $this->validateProjectAndUser($request);
86
        if ($ret instanceof RedirectResponse) {
0 ignored issues
show
Bug introduced by
The class AppBundle\Controller\RedirectResponse does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
87
            return $ret;
88
        } else {
89
            list($projectData, $user) = $ret;
90
        }
91
92
        $editSummaryUsage = $this->getEditSummaryUsage($projectData, $user, $namespace);
93
94
        // Assign the values and display the template
95
        return $this->render(
96
            'editSummary/result.html.twig',
97
            array_merge($editSummaryUsage, [
98
                'xtPage' => 'es',
99
                'xtTitle' => $user->getUsername(),
100
                'user' => $user,
101
                'project' => $projectData,
102
                'namespace' => $namespace,
103
            ])
104
        );
105
    }
106
107
    /**
108
     * Get data on edit summary usage of the given user
109
     * @param  Project $project
110
     * @param  User $user
111
     * @param  string $namespace
112
     * @return array
113
     * @todo Should we move this to an actual Repository? Very specific to this controller
114
     */
115
    private function getEditSummaryUsage(Project $project, User $user, $namespace)
116
    {
117
        $dbName = $project->getDatabaseName();
118
119
        $cacheKey = 'editsummaryusage.' . $dbName . '.'
120
            . $user->getCacheKey() . '.' . $namespace;
121
122
        $cache = $this->container->get('cache.app');
123
        if ($cache->hasItem($cacheKey)) {
124
            return $cache->getItem($cacheKey)->get();
125
        }
126
127
        // Load the database tables
128
        $revisionTable = $project->getRepository()->getTableName($dbName, 'revision');
129
        $pageTable = $project->getRepository()->getTableName($dbName, 'page');
130
131
        /**
132
         * Connection to the replica database
133
         *
134
         * @var Connection $conn
135
         */
136
        $conn = $this->get('doctrine')->getManager('replicas')->getConnection();
137
138
        $condNamespace = $namespace === 'all' ? '' : 'AND page_namespace = :namespace';
139
        $pageJoin = $namespace === 'all' ? '' : "JOIN $pageTable ON rev_page = page_id";
140
        $username = $user->getUsername();
141
142
        // Prepare the query and execute
143
        $sql = "SELECT rev_comment, rev_timestamp, rev_minor_edit
144
                FROM  $revisionTable
145
    ​            $pageJoin
146
                WHERE rev_user_text = :username
147
                $condNamespace
148
                ORDER BY rev_timestamp DESC";
149
150
        $resultQuery = $conn->prepare($sql);
151
        $resultQuery->bindParam('username', $username);
152
        if ($namespace !== 'all') {
153
            $resultQuery->bindParam('namespace', $namespace);
154
        }
155
        $resultQuery->execute();
156
157 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...
158
            $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...
159
            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...
160
                'EditSummaryProject',
161
                [
162
                    'project' => $project->getDomain()
163
                ]
164
            );
165
        }
166
167
        // Set defaults, so we don't get variable undefined errors
168
        $totalSummariesMajor = 0;
169
        $totalSummariesMinor = 0;
170
        $totalEditsMajor = 0;
171
        $totalEditsMinor = 0;
172
        $recentEditsMajor = 0;
173
        $recentEditsMinor = 0;
174
        $recentSummariesMajor = 0;
175
        $recentSummariesMinor = 0;
176
        $monthTotals = [];
177
        $monthEditsummaryTotals = [];
178
        $totalEdits = 0;
179
        $totalSummaries = 0;
180
181
        while ($row = $resultQuery->fetch()) {
182
            // Extract the date out of the date field
183
            $timestamp = DateTime::createFromFormat('YmdHis', $row['rev_timestamp']);
184
185
            $monthkey = date_format($timestamp, 'Y-m');
186
187
            // Check and see if the month is set for all major edits edits.
188
            // If not, default it to 1.
189
            if (!isset($monthTotals[$monthkey])) {
190
                $monthTotals[$monthkey] = 1;
191
            } else {
192
                $monthTotals[$monthkey]++;
193
            }
194
195
            // Grand total for number of edits
196
            $totalEdits++;
197
198
            // Total edit summaries
199
            if ($row['rev_comment'] !== '') {
200
                $totalSummaries++;
201
            }
202
203
            // Now do the same, if we have an edit summary
204
            if ($row['rev_minor_edit'] == 0) {
205 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...
206
                    isset($monthEditsummaryTotals[$monthkey]) ?
207
                        $monthEditsummaryTotals[$monthkey]++ :
208
                        $monthEditsummaryTotals[$monthkey] = 1;
209
                    $totalSummariesMajor++;
210
                }
211
212
                // Now do the same for recent edits
213
                $totalEditsMajor++;
214
                if ($recentEditsMajor < 150) {
215
                    $recentEditsMajor++;
216
                    if ($row['rev_comment'] != '') {
217
                        $recentSummariesMajor++;
218
                    }
219
                }
220
            } else {
221
                // The exact same procedure as documented above for minor edits
222
                // If there is a comment, count it
223 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...
224
                    isset($monthEditsummaryTotals[$monthkey]) ?
225
                        $monthEditsummaryTotals[$monthkey]++ :
226
                        $monthEditsummaryTotals[$monthkey] = 1;
227
                    $totalSummariesMinor++;
228
                    $totalEditsMinor++;
229
                } else {
230
                    $totalEditsMinor++;
231
                }
232
233
                // Handle recent edits
234
                if ($recentEditsMinor < 150) {
235
                    $recentEditsMinor++;
236
                    if ($row['rev_comment'] != '') {
237
                        $recentSummariesMinor++;
238
                    }
239
                }
240
            }
241
        }
242
243
        $result = [
244
            'totalEdits' => $totalEdits,
245
            'totalEditsMajor' => $totalEditsMajor,
246
            'totalEditsMinor' => $totalEditsMinor,
247
            'totalSummaries' => $totalSummaries,
248
            'totalSummariesMajor' => $totalSummariesMajor,
249
            'totalSummariesMinor' => $totalSummariesMinor,
250
            'recentEditsMajor' => $recentEditsMajor,
251
            'recentEditsMinor' => $recentEditsMinor,
252
            'recentSummariesMajor' => $recentSummariesMajor,
253
            'recentSummariesMinor' => $recentSummariesMinor,
254
            'monthTotals' => $monthTotals,
255
            'monthEditSumTotals' => $monthEditsummaryTotals,
256
        ];
257
258
        // Cache for 10 minutes, and return.
259
        $cacheItem = $cache->getItem($cacheKey)
260
            ->set($result)
261
            ->expiresAfter(new DateInterval('PT10M'));
262
        $cache->save($cacheItem);
263
264
        return $result;
265
    }
266
}
267