Passed
Push — master ( 902a34...c826a7 )
by Kyle
53s queued 11s
created

php/PHPMD/Rule/Design/TooManyPublicMethods.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * This file is part of PHP Mess Detector.
4
 *
5
 * Copyright (c) Manuel Pichler <[email protected]>.
6
 * All rights reserved.
7
 *
8
 * Licensed under BSD License
9
 * For full copyright and license information, please see the LICENSE file.
10
 * Redistributions of files must retain the above copyright notice.
11
 *
12
 * @author Manuel Pichler <[email protected]>
13
 * @copyright Manuel Pichler. All rights reserved.
14
 * @license https://opensource.org/licenses/bsd-license.php BSD License
15
 * @link http://phpmd.org/
16
 */
17
18
namespace PHPMD\Rule\Design;
19
20
use PHPMD\AbstractNode;
21
use PHPMD\AbstractRule;
22
use PHPMD\Node\AbstractTypeNode;
23
use PHPMD\Rule\ClassAware;
24
25
/**
26
 * This rule class will detect all classes with too much public methods.
27
 */
28
class TooManyPublicMethods extends AbstractRule implements ClassAware
29
{
30
    /**
31
     * Regular expression that filters all methods that are ignored by this rule.
32
     *
33
     * @var string
34
     */
35
    protected $ignoreRegexp;
36
37
    /**
38
     * This method checks the number of public methods with in a given class and checks
39
     * this number against a configured threshold.
40
     *
41
     * @param \PHPMD\AbstractNode $node
42
     * @return void
43
     */
44 13
    public function apply(AbstractNode $node)
45
    {
46 13
        $this->ignoreRegexp = $this->getStringProperty('ignorepattern');
47
48 13
        $threshold = $this->getIntProperty('maxmethods');
49 13
        $publicMethodsCount = $node->getMetric('npm'); // NPM stands for Number of Public Methods
50 3
51
        if ($publicMethodsCount !== null && $publicMethodsCount <= $threshold) {
52
            return;
53 10
        }
54 10
55 7
        /** @var AbstractTypeNode $node */
56
        $nom = $this->countMethods($node);
57 3
58 3
        if ($nom <= $threshold) {
59
            return;
60 3
        }
61 3
62 3
        $this->addViolation(
63 3
            $node,
64
            array(
65
                $node->getType(),
66 3
                $node->getName(),
67
                $nom,
68
                $threshold,
69
            )
70
        );
71
    }
72
73
    /**
74 10
     * Counts public methods within the given class/interface node.
75
     *
76 10
     * @param \PHPMD\Node\AbstractTypeNode $node
77 10
     * @return integer
78 10
     */
79 10
    protected function countMethods(AbstractTypeNode $node)
80
    {
81
        $count = 0;
82 10
        foreach ($node->getMethods() as $method) {
83
            if ($method->getNode()->isPublic() && !$this->isIgnoredMethodName($method->getName())) {
0 ignored issues
show
It seems like you code against a concrete implementation and not the interface PDepend\Source\AST\ASTArtifact as the method isPublic() does only exist in the following implementations of said interface: PDepend\Source\AST\ASTMethod, PDepend\Source\AST\ASTProperty.

Let’s take a look at an example:

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

class MyUser implements 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 implementation 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 interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
84
                ++$count;
85
            }
86
        }
87
88
        return $count;
89
    }
90
91
    /**
92
     * Return true if the method name is ignored by the current ignoreRegexp pattern.
93
     *
94
     * ignoreRegexp is given to the rule using ignorepattern option.
95
     *
96
     * @param string $methodName
97
     * @return bool
98
     */
99
    private function isIgnoredMethodName($methodName)
100
    {
101
        return !empty($this->ignoreRegexp) &&
102
            preg_match($this->ignoreRegexp, $methodName) === 1;
103
    }
104
}
105