Completed
Push — master ( 9b18dc...e19264 )
by Tomáš
18s
created

FinalInterfaceSniff::process()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 4.0092

Importance

Changes 2
Bugs 0 Features 2
Metric Value
c 2
b 0
f 2
dl 0
loc 22
ccs 11
cts 12
cp 0.9167
rs 8.9197
cc 4
eloc 12
nc 4
nop 2
crap 4.0092
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 PHP_CodeSniffer_File
29
	 */
30
	private $file;
31
32
	/**
33
	 * @var int
34
	 */
35
	private $position;
36
37
38
	/**
39
	 * {@inheritdoc}
40
	 */
41 1
	public function register()
42
	{
43 1
		return [T_CLASS];
44
	}
45
46
47
	/**
48
	 * {@inheritdoc}
49
	 */
50 1
	public function process(PHP_CodeSniffer_File $file, $position)
51
	{
52 1
		$this->file = $file;
53 1
		$this->position = $position;
54
55 1
		if ($this->implementsInterface() === FALSE) {
56
			return;
57
		}
58
59 1
		if ($this->isFinalOrAbstractClass()) {
60 1
			return;
61
		}
62
63 1
		if ($this->isDoctrineEntity()) {
64 1
			return;
65
		}
66
67 1
		$file->addError(
68 1
			'Non-abstract class that implements interface should be final.',
69
			$position
70
		);
71 1
	}
72
73
74
	/**
75
	 * @return bool
76
	 */
77 1
	private function implementsInterface()
78
	{
79 1
		return (bool) $this->file->findNext(T_IMPLEMENTS, $this->position);
80
	}
81
82
83
	/**
84
	 * @return bool
85
	 */
86 1
	private function isFinalOrAbstractClass()
87
	{
88 1
		$classProperties = $this->file->getClassProperties($this->position);
89 1
		return ($classProperties['is_abstract'] || $classProperties['is_final']);
90
	}
91
92
93
	/**
94
	 * @return bool
95
	 */
96 1
	private function isDoctrineEntity()
97
	{
98 1
		$docCommentPosition = $this->file->findPrevious(T_DOC_COMMENT_OPEN_TAG, $this->position);
99 1
		if ($docCommentPosition === FALSE) {
100 1
			return FALSE;
101
		}
102
103 1
		$seekPosition = $docCommentPosition;
104
105
		do {
106 1
			$docCommentTokenContent = $this->file->getTokens()[$docCommentPosition]['content'];
107 1
			if (strpos($docCommentTokenContent, 'Entity') !== FALSE) {
108 1
				return TRUE;
109
			}
110 1
			$seekPosition++;
111
112 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 110 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...
113
114
		return FALSE;
115
	}
116
117
}
118