Completed
Push — master ( 1a6e67...071a29 )
by Tomáš
02:38
created

FinalInterfaceSniff   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 107
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Test Coverage

Coverage 94.12%

Importance

Changes 0
Metric Value
wmc 14
lcom 1
cbo 2
dl 0
loc 107
ccs 32
cts 34
cp 0.9412
rs 10
c 0
b 0
f 0

6 Methods

Rating   Name   Duplication   Size   Complexity  
A register() 0 4 1
B process() 0 23 5
A implementsInterface() 0 4 1
A isFinalOrAbstractClass() 0 5 2
A isDoctrineEntity() 0 20 4
A addFinalToClass() 0 4 1
1
<?php
2
3
declare(strict_types = 1);
4
5
/*
6
 * This file is part of Zenify
7
 * Copyright (c) 2012 Tomas Votruba (http://tomasvotruba.cz)
8
 */
9
10
namespace ZenifyCodingStandard\Sniffs\Classes;
11
12
use PHP_CodeSniffer_File;
13
use PHP_CodeSniffer_Sniff;
14
15
16
/**
17
 * Rules:
18
 * - Non-abstract class that implements interface should be final.
19
 * - Except for Doctrine entities, they cannot be final.
20
 *
21
 * Inspiration:
22
 * - http://ocramius.github.io/blog/when-to-declare-classes-final/
23
 */
24
final class FinalInterfaceSniff implements PHP_CodeSniffer_Sniff
25
{
26
27
	/**
28
	 * @var string
29
	 */
30
	const NAME = 'ZenifyCodingStandard.Classes.FinalInterface';
31
32
	/**
33
	 * @var PHP_CodeSniffer_File
34
	 */
35
	private $file;
36
37
	/**
38
	 * @var int
39
	 */
40
	private $position;
41
42
43
	/**
44
	 * @return int[]
45
	 */
46 2
	public function register() : array
47
	{
48 2
		return [T_CLASS];
49
	}
50
51
52
	/**
53
	 * @param PHP_CodeSniffer_File $file
54
	 * @param int $position
55
	 */
56 2
	public function process(PHP_CodeSniffer_File $file, $position)
57
	{
58 2
		$this->file = $file;
59 2
		$this->position = $position;
60
61 2
		if ($this->implementsInterface() === FALSE) {
62
			return;
63
		}
64
65 2
		if ($this->isFinalOrAbstractClass()) {
66 2
			return;
67
		}
68
69 2
		if ($this->isDoctrineEntity()) {
70 1
			return;
71
		}
72
73 2
		$fix = $file->addFixableError('Non-abstract class that implements interface should be final.', $position);
74
75 2
		if ($fix) {
76 1
			$this->addFinalToClass($position);
77
		}
78 2
	}
79
80
81
	/**
82
	 * @return bool
83
	 */
84 2
	private function implementsInterface()
85
	{
86 2
		return (bool) $this->file->findNext(T_IMPLEMENTS, $this->position);
87
	}
88
89
90
	/**
91
	 * @return bool
92
	 */
93 2
	private function isFinalOrAbstractClass()
94
	{
95 2
		$classProperties = $this->file->getClassProperties($this->position);
96 2
		return ($classProperties['is_abstract'] || $classProperties['is_final']);
97
	}
98
99
100
	/**
101
	 * @return bool
102
	 */
103 2
	private function isDoctrineEntity()
104
	{
105 2
		$docCommentPosition = $this->file->findPrevious(T_DOC_COMMENT_OPEN_TAG, $this->position);
106 2
		if ($docCommentPosition === FALSE) {
107 2
			return FALSE;
108
		}
109
110 1
		$seekPosition = $docCommentPosition;
111
112
		do {
113 1
			$docCommentTokenContent = $this->file->getTokens()[$docCommentPosition]['content'];
114 1
			if (strpos($docCommentTokenContent, 'Entity') !== FALSE) {
115 1
				return TRUE;
116
			}
117 1
			$seekPosition++;
118
119 1
		} while ($docCommentPosition = $this->file->findNext(T_DOC_COMMENT_TAG, $seekPosition, $this->position));
0 ignored issues
show
Bug introduced by
It seems like $seekPosition defined by $seekPosition++ on line 117 can also be of type boolean; however, PHP_CodeSniffer_File::findNext() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
120
121
		return FALSE;
122
	}
123
124
125 1
	public function addFinalToClass(int $position)
126
	{
127 1
		$this->file->fixer->addContentBefore($position, 'final ');
128 1
	}
129
130
}
131