Completed
Push — master ( 1ec99b...a17ead )
by Kevin
02:44
created

AbstractToken::hasAncestor()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 13
ccs 0
cts 7
cp 0
rs 9.2
cc 4
eloc 7
nc 3
nop 1
crap 20
1
<?php
2
3
namespace Groundskeeper\Tokens;
4
5
use Groundskeeper\Configuration;
6
use Groundskeeper\Exceptions\ValidationException;
7
use Psr\Log\LoggerInterface;
8
9
/**
10
 * A base class for all tokens.
11
 */
12
abstract class AbstractToken implements Token
13
{
14
    /** @var Configuration */
15
    protected $configuration;
16
17
    /** @var int */
18
    private $depth;
19
20
    /** @var null|Token */
21
    private $parent;
22
23
    /** @var string */
24
    private $type;
25
26
    /**
27
     * Constructor
28
     */
29 65
    public function __construct($type, Configuration $configuration)
30
    {
31
        if ($type !== Token::CDATA
32 65
            && $type !== Token::COMMENT
33 65
            && $type !== Token::DOCTYPE
34 65
            && $type !== Token::ELEMENT
35 65
            && $type !== Token::TEXT) {
36 1
            throw new \InvalidArgumentException('Invalid type: ' . $type);
37
        }
38
39 64
        $this->configuration = $configuration;
40 64
        $this->depth = 0;
41 64
        $this->parent = null;
42 64
        $this->type = $type;
43 64
    }
44
45
    /**
46
     * Required by the Token interface.
47
     */
48 40
    public function getDepth()
49
    {
50 40
        return $this->depth;
51
    }
52
53
    /**
54
     * Required by the Token interface.
55
     */
56 20
    public function getParent()
57
    {
58 20
        return $this->parent;
59
    }
60
61
    /**
62
     * Chainable setter for 'parent'.
63
     */
64 39
    public function setParent(Token $parent = null)
65
    {
66 39
        $this->depth = 0;
67 39
        if ($parent instanceof Token) {
68 39
            $this->depth = $parent->getDepth() + 1;
69 39
        }
70
71 39
        $this->parent = $parent;
72
73 39
        return $this;
74
    }
75
76
    public function hasAncestor(Element $element)
77
    {
78
        if ($this->parent === null) {
79
            return false;
80
        }
81
82
        if ($this->parent->getType() == Token::ELEMENT &&
83
            $this->parent->getName() == $element->getName()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Groundskeeper\Tokens\Token as the method getName() does only exist in the following implementations of said interface: Groundskeeper\Tokens\Element, Groundskeeper\Tokens\ElementTypes\ClosedElement, Groundskeeper\Tokens\ElementTypes\OpenElement, Groundskeeper\Tokens\Elements\Article, Groundskeeper\Tokens\Elements\Aside, Groundskeeper\Tokens\Elements\Base, Groundskeeper\Tokens\Elements\Body, Groundskeeper\Tokens\Elements\Br, Groundskeeper\Tokens\Elements\Footer, Groundskeeper\Tokens\Elements\H1, Groundskeeper\Tokens\Elements\H2, Groundskeeper\Tokens\Elements\H3, Groundskeeper\Tokens\Elements\H4, Groundskeeper\Tokens\Elements\H5, Groundskeeper\Tokens\Elements\H6, Groundskeeper\Tokens\Elements\Head, Groundskeeper\Tokens\Elements\Header, Groundskeeper\Tokens\Elements\Hgroup, Groundskeeper\Tokens\Elements\Hr, Groundskeeper\Tokens\Elements\Html, Groundskeeper\Tokens\Elements\Link, Groundskeeper\Tokens\Elements\Main, Groundskeeper\Tokens\Elements\Meta, Groundskeeper\Tokens\Elements\Nav, Groundskeeper\Tokens\Elements\Noscript, Groundskeeper\Tokens\Elements\Script, Groundskeeper\Tokens\Elements\Section, Groundskeeper\Tokens\Elements\Style, Groundskeeper\Tokens\Elements\Template, Groundskeeper\Tokens\Elements\Title.

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
            return true;
85
        }
86
87
        return $this->parent->hasAncestor($element);
88
    }
89
90
    /**
91
     * Required by the Token interface.
92
     */
93 27
    public function getType()
94
    {
95 27
        return $this->type;
96
    }
97
98 45
    public static function cleanChildTokens(Configuration $configuration, array &$children, LoggerInterface $logger = null)
99
    {
100 45
        if ($configuration->get('clean-strategy') == Configuration::CLEAN_STRATEGY_NONE) {
101 33
            return true;
102
        }
103
104 44
        foreach ($children as $key => $child) {
105 40
            if ($child instanceof Cleanable) {
106 34
                $isClean = $child->clean($logger);
107 34
                if (!$isClean  && $configuration->get('clean-strategy') !== Configuration::CLEAN_STRATEGY_LENIENT) {
108 2
                    if ($logger !== null) {
109 2
                        $logger->debug('Unable to fix.  Removing ' . $child);
110 2
                    }
111
112 2
                    unset($children[$key]);
113 2
                }
114 34
            }
115 44
        }
116
117 44
        return true;
118 1
    }
119
120 5
    public function __toString()
121
    {
122 5
        return ucfirst($this->getType());
123
    }
124
}
125