Completed
Pull Request — master (#99)
by MusikAnimal
02:26
created

EditCounterController::timecardAction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 21
Code Lines 16

Duplication

Lines 21
Ratio 100 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 21
loc 21
rs 9.3142
cc 2
eloc 16
nc 2
nop 2
1
<?php
2
/**
3
 * This file contains only the EditCounterController class.
4
 */
5
6
namespace AppBundle\Controller;
7
8
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
9
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
10
use Symfony\Component\HttpFoundation\Request;
11
use Symfony\Component\HttpFoundation\Response;
12
use Symfony\Component\HttpFoundation\RedirectResponse;
13
use Symfony\Component\HttpFoundation\JsonResponse;
14
use Xtools\EditCounter;
15
use Xtools\EditCounterRepository;
16
use Xtools\Page;
17
use Xtools\Project;
18
use Xtools\ProjectRepository;
19
use Xtools\User;
20
use Xtools\UserRepository;
21
22
/**
23
 * Class EditCounterController
24
 */
25
class EditCounterController extends Controller
26
{
27
28
    /** @var User The user being queried. */
29
    protected $user;
30
31
    /** @var Project The project being queried. */
32
    protected $project;
33
34
    /** @var EditCounter The edit-counter, that does all the work. */
35
    protected $editCounter;
36
37
    /**
38
     * Get the tool's shortname.
39
     * @return string
40
     */
41
    public function getToolShortname()
42
    {
43
        return 'ec';
44
    }
45
46
    /**
47
     * Every action in this controller (other than 'index') calls this first.
48
     * If a response is returned, the calling action is expected to return it.
49
     * @param string|bool $project The project name.
50
     * @param string|bool $username The username.
51
     * @return null|RedirectResponse
52
     */
53
    protected function setUpEditCounter($project = false, $username = false)
54
    {
55
        $this->project = ProjectRepository::getProject($project, $this->container);
0 ignored issues
show
Bug introduced by
It seems like $project defined by parameter $project on line 53 can also be of type boolean; however, Xtools\ProjectRepository::getProject() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
56
        $this->user = UserRepository::getUser($username, $this->container);
1 ignored issue
show
Bug introduced by
It seems like $username defined by parameter $username on line 53 can also be of type boolean; however, Xtools\UserRepository::getUser() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Compatibility introduced by
$this->container of type object<Symfony\Component...ion\ContainerInterface> is not a sub-type of object<Symfony\Component...ncyInjection\Container>. It seems like you assume a concrete implementation of the interface Symfony\Component\Depend...tion\ContainerInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
57
58
        // Don't continue if the user doesn't exist.
59
        if (!$this->user->existsOnProject($this->project)) {
60
            $this->addFlash('notice', 'user-not-found');
61
            return $this->redirectToRoute('ec', ['project' => $project]);
62
        }
63
64
        // Get an edit-counter if we don't already have it set.
65
        if ($this->editCounter instanceof EditCounter) {
66
            return;
67
        }
68
        $editCounterRepo = new EditCounterRepository();
69
        $editCounterRepo->setContainer($this->container);
1 ignored issue
show
Compatibility introduced by
$this->container of type object<Symfony\Component...ion\ContainerInterface> is not a sub-type of object<Symfony\Component...ncyInjection\Container>. It seems like you assume a concrete implementation of the interface Symfony\Component\Depend...tion\ContainerInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
70
        $this->editCounter = new EditCounter($this->project, $this->user);
71
        $this->editCounter->setRepository($editCounterRepo);
72
    }
73
74
    /**
75
     * The initial GET request that displays the search form.
76
     *
77
     * @Route("/ec", name="ec")
78
     * @Route("/ec", name="EditCounter")
79
     * @Route("/ec/", name="EditCounterSlash")
80
     * @Route("/ec/index.php", name="EditCounterIndexPhp")
81
     * @Route("/ec/{project}", name="EditCounterProject")
82
     *
83
     * @param Request $request
84
     * @param string|null $project
85
     * @return RedirectResponse|Response
86
     */
87
    public function indexAction(Request $request, $project = null)
88
    {
89
        $queryProject = $request->query->get('project');
90
        $username = $request->query->get('username', $request->query->get('user'));
91
92
        if (($project || $queryProject) && $username) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $project of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
93
            $routeParams = ['project' => ($project ?: $queryProject), 'username' => $username];
94
            return $this->redirectToRoute('EditCounterResult', $routeParams);
95
        } elseif (!$project && $queryProject) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $project of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
96
            return $this->redirectToRoute('EditCounterProject', ['project' => $queryProject]);
97
        }
98
99
        $project = ProjectRepository::getProject($project, $this->container);
100
        if (!$project->exists()) {
101
            $project = ProjectRepository::getDefaultProject($this->container);
102
        }
103
104
        // Otherwise fall through.
105
        return $this->render('editCounter/index.html.twig', [
106
            'xtPageTitle' => 'tool-ec',
107
            'xtSubtitle' => 'tool-ec-desc',
108
            'xtPage' => 'ec',
109
            'project' => $project,
110
        ]);
111
    }
112
113
    /**
114
     * Display all results.
115
     * @Route("/ec/{project}/{username}", name="EditCounterResult")
116
     * @param Request $request
117
     * @param string $project
118
     * @param string $username
119
     * @return Response
120
     */
121
    public function resultAction(Request $request, $project, $username)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
122
    {
123
        $ret = $this->setUpEditCounter($project, $username);
124
        if ($ret instanceof RedirectResponse) {
125
            return $ret;
126
        }
127
128
        // Asynchronously collect some of the data that will be shown.
129
        // If multithreading is turned off, the normal getters in the views will
130
        // collect the necessary data synchronously.
131
        if ($this->container->getParameter('app.multithread')) {
132
            $this->editCounter->prepareData($this->container);
0 ignored issues
show
Unused Code introduced by
The call to EditCounter::prepareData() has too many arguments starting with $this->container.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
133
        }
134
135
        // FIXME: is this needed? It shouldn't ever be a subrequest here in the resultAction.
136
        $isSubRequest = $this->container->get('request_stack')->getParentRequest() !== null;
137
138
        return $this->render('editCounter/result.html.twig', [
139
            'xtTitle' => $this->user->getUsername() . ' - ' . $this->project->getTitle(),
140
            'xtPage' => 'ec',
141
            'base_dir' => realpath($this->getParameter('kernel.root_dir').'/..'),
142
            'is_sub_request' => $isSubRequest,
143
            'user' => $this->user,
144
            'project' => $this->project,
145
            'ec' => $this->editCounter,
146
        ]);
147
    }
148
149
    /**
150
     * Display the general statistics section.
151
     * @Route("/ec-generalstats/{project}/{username}", name="EditCounterGeneralStats")
152
     * @param string $project
153
     * @param string $username
154
     * @return Response
155
     */
156 View Code Duplication
    public function generalStatsAction($project, $username)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
157
    {
158
        $ret = $this->setUpEditCounter($project, $username);
159
        if ($ret instanceof RedirectResponse) {
160
            return $ret;
161
        }
162
163
        $isSubRequest = $this->get('request_stack')->getParentRequest() !== null;
164
        return $this->render('editCounter/general_stats.html.twig', [
165
            'xtTitle' => $this->user->getUsername(),
166
            'xtPage' => 'ec',
167
            'is_sub_request' => $isSubRequest,
168
            'user' => $this->user,
169
            'project' => $this->project,
170
            'ec' => $this->editCounter,
171
        ]);
172
    }
173
174
    /**
175
     * Display the namespace totals section.
176
     * @Route("/ec-namespacetotals/{project}/{username}", name="EditCounterNamespaceTotals")
177
     * @param Request $request
178
     * @param string $project
179
     * @param string $username
180
     * @return Response
181
     */
182 View Code Duplication
    public function namespaceTotalsAction(Request $request, $project, $username)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in 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...
183
    {
184
        $ret = $this->setUpEditCounter($project, $username);
185
        if ($ret instanceof RedirectResponse) {
186
            return $ret;
187
        }
188
189
        $isSubRequest = $this->get('request_stack')->getParentRequest() !== null;
190
        return $this->render('editCounter/namespace_totals.html.twig', [
191
            'xtTitle' => $this->user->getUsername(),
192
            'xtPage' => 'ec',
193
            'is_sub_request' => $isSubRequest,
194
            'user' => $this->user,
195
            'project' => $this->project,
196
            'ec' => $this->editCounter,
197
        ]);
198
    }
199
200
    /**
201
     * Display the timecard section.
202
     * @Route("/ec-timecard/{project}/{username}", name="EditCounterTimecard")
203
     * @param string $project
204
     * @param string $username
205
     * @return Response
206
     */
207 View Code Duplication
    public function timecardAction($project, $username)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
208
    {
209
        $ret = $this->setUpEditCounter($project, $username);
210
        if ($ret instanceof RedirectResponse) {
211
            return $ret;
212
        }
213
214
        $isSubRequest = $this->get('request_stack')->getParentRequest() !== null;
215
        $optedInPage = $this->project
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xtools\Repository as the method getPage() does only exist in the following sub-classes of Xtools\Repository: Xtools\ProjectRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
216
            ->getRepository()
217
            ->getPage($this->project, $this->project->userOptInPage($this->user));
218
        return $this->render('editCounter/timecard.html.twig', [
219
            'xtTitle' => $this->user->getUsername(),
220
            'xtPage' => 'ec',
221
            'is_sub_request' => $isSubRequest,
222
            'user' => $this->user,
223
            'project' => $this->project,
224
            'ec' => $this->editCounter,
225
            'opted_in_page' => $optedInPage,
226
        ]);
227
    }
228
229
    /**
230
     * Display the year counts section.
231
     * @Route("/ec-yearcounts/{project}/{username}", name="EditCounterYearCounts")
232
     * @param string $project
233
     * @param string $username
234
     * @return Response
235
     */
236 View Code Duplication
    public function yearcountsAction($project, $username)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
237
    {
238
        $ret = $this->setUpEditCounter($project, $username);
239
        if ($ret instanceof RedirectResponse) {
240
            return $ret;
241
        }
242
243
        $isSubRequest = $this->container->get('request_stack')->getParentRequest() !== null;
244
        return $this->render('editCounter/yearcounts.html.twig', [
245
            'xtTitle' => $this->user->getUsername(),
246
            'xtPage' => 'ec',
247
            'is_sub_request' => $isSubRequest,
248
            'user' => $this->user,
249
            'project' => $this->project,
250
            'ec' => $this->editCounter,
251
        ]);
252
    }
253
254
    /**
255
     * Display the month counts section.
256
     * @Route("/ec-monthcounts/{project}/{username}", name="EditCounterMonthCounts")
257
     * @param Request $request
258
     * @param string $project
259
     * @param string $username
260
     * @return Response
261
     */
262 View Code Duplication
    public function monthcountsAction(Request $request, $project, $username)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in 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...
263
    {
264
        $ret = $this->setUpEditCounter($project, $username);
265
        if ($ret instanceof RedirectResponse) {
266
            return $ret;
267
        }
268
269
        $isSubRequest = $this->container->get('request_stack')->getParentRequest() !== null;
270
        $optedInPage = $this->project
1 ignored issue
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Xtools\Repository as the method getPage() does only exist in the following sub-classes of Xtools\Repository: Xtools\ProjectRepository. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
271
            ->getRepository()
272
            ->getPage($this->project, $this->project->userOptInPage($this->user));
273
        return $this->render('editCounter/monthcounts.html.twig', [
274
            'xtTitle' => $this->user->getUsername(),
275
            'xtPage' => 'ec',
276
            'is_sub_request' => $isSubRequest,
277
            'user' => $this->user,
278
            'project' => $this->project,
279
            'ec' => $this->editCounter,
280
            'opted_in_page' => $optedInPage,
281
        ]);
282
    }
283
284
    /**
285
     * Display the latest global edits section.
286
     * @Route("/ec-latestglobal/{project}/{username}", name="EditCounterLatestGlobal")
287
     * @param Request $request The HTTP request.
288
     * @param string $project The project name.
289
     * @param string $username The username.
290
     * @return Response
291
     */
292 View Code Duplication
    public function latestglobalAction(Request $request, $project, $username)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
293
    {
294
        $ret = $this->setUpEditCounter($project, $username);
295
        if ($ret instanceof RedirectResponse) {
296
            return $ret;
297
        }
298
299
        $isSubRequest = $request->get('htmlonly')
300
                        || $this->container->get('request_stack')->getParentRequest() !== null;
301
        return $this->render('editCounter/latest_global.html.twig', [
302
            'xtTitle' => $this->user->getUsername(),
303
            'xtPage' => 'ec',
304
            'is_sub_request' => $isSubRequest,
305
            'user' => $this->user,
306
            'project' => $this->project,
307
            'ec' => $this->editCounter,
308
        ]);
309
    }
310
311
312
    /**
313
     * Below are internal API endpoints for the Edit Counter.
314
     * All only respond with JSON and only to requests from the localhost
315
     * (see access_control in security.yml).
316
     */
317
318
    /**
319
     * Get (most) of the general statistics as JSON.
320
     * @Route("/api/ec/pairdata/{project}/{username}", name="EditCounterApiPairData")
321
     * @param Request $request
322
     * @param string $project
323
     * @param string $username
324
     * @return JsonResponse
325
     */
326 View Code Duplication
    public function pairDataApiAction(Request $request, $project, $username)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in 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...
327
    {
328
        $ret = $this->setUpEditCounter($project, $username);
329
        if ($ret instanceof RedirectResponse) {
330
            return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $ret; (Symfony\Component\HttpFoundation\RedirectResponse) is incompatible with the return type documented by AppBundle\Controller\Edi...ller::pairDataApiAction of type Symfony\Component\HttpFoundation\JsonResponse.

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...
331
        }
332
333
        return new JsonResponse(
334
            $this->editCounter->getPairData(),
335
            Response::HTTP_OK
336
        );
337
    }
338
339
    /**
340
     * Get various log counts for the user as JSON.
341
     * @Route("/api/ec/logcounts/{project}/{username}", name="EditCounterApiLogCounts")
342
     * @param Request $request
343
     * @param string $project
344
     * @param string $username
345
     * @return JsonResponse
346
     */
347 View Code Duplication
    public function logCountsApiAction(Request $request, $project, $username)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in 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...
348
    {
349
        $ret = $this->setUpEditCounter($project, $username);
350
        if ($ret instanceof RedirectResponse) {
351
            return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $ret; (Symfony\Component\HttpFoundation\RedirectResponse) is incompatible with the return type documented by AppBundle\Controller\Edi...ler::logCountsApiAction of type Symfony\Component\HttpFoundation\JsonResponse.

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...
352
        }
353
354
        return new JsonResponse(
355
            $this->editCounter->getLogCounts(),
356
            Response::HTTP_OK
357
        );
358
    }
359
360
    /**
361
     * Get edit sizes for the user as JSON.
362
     * @Route("/api/ec/editsizes/{project}/{username}", name="EditCounterApiEditSizes")
363
     * @param Request $request
364
     * @param string $project
365
     * @param string $username
366
     * @return JsonResponse
367
     */
368 View Code Duplication
    public function editSizesApiAction(Request $request, $project, $username)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in 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...
369
    {
370
        $ret = $this->setUpEditCounter($project, $username);
371
        if ($ret instanceof RedirectResponse) {
372
            return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $ret; (Symfony\Component\HttpFoundation\RedirectResponse) is incompatible with the return type documented by AppBundle\Controller\Edi...ler::editSizesApiAction of type Symfony\Component\HttpFoundation\JsonResponse.

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...
373
        }
374
375
        return new JsonResponse(
376
            $this->editCounter->getEditSizeData(),
377
            Response::HTTP_OK
378
        );
379
    }
380
381
    /**
382
     * Get the namespace totals for the user as JSON.
383
     * @Route("/api/ec/namespacetotals/{project}/{username}", name="EditCounterApiNamespaceTotals")
384
     * @param Request $request
385
     * @param string $project
386
     * @param string $username
387
     * @return Response
388
     */
389 View Code Duplication
    public function namespaceTotalsApiAction(Request $request, $project, $username)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in 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...
390
    {
391
        $ret = $this->setUpEditCounter($project, $username);
392
        if ($ret instanceof RedirectResponse) {
393
            return $ret;
394
        }
395
396
        return new JsonResponse(
397
            $this->editCounter->namespaceTotals(),
398
            Response::HTTP_OK
399
        );
400
    }
401
402
    /**
403
     * Display or fetch the month counts for the user.
404
     * @Route("/api/ec/monthcounts/{project}/{username}", name="EditCounterApiMonthCounts")
405
     * @param Request $request
406
     * @param string $project
407
     * @param string $username
408
     * @return Response
409
     */
410 View Code Duplication
    public function monthcountsApiAction(Request $request, $project, $username)
0 ignored issues
show
Unused Code introduced by
The parameter $request is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in 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...
411
    {
412
        $ret = $this->setUpEditCounter($project, $username);
413
        if ($ret instanceof RedirectResponse) {
414
            return $ret;
415
        }
416
417
        return new JsonResponse(
418
            $this->editCounter->monthCounts(),
419
            Response::HTTP_OK
420
        );
421
    }
422
}
423