Completed
Pull Request — master (#41)
by Tomáš
07:50 queued 04:44
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
/*
4
 * This file is part of Zenify
5
 * Copyright (c) 2012 Tomas Votruba (http://tomasvotruba.cz)
6
 */
7
8
namespace ZenifyCodingStandard\Sniffs\Classes;
9
10
use PHP_CodeSniffer_File;
11
use PHP_CodeSniffer_Sniff;
12
13
14
/**
15
 * Rules:
16
 * - Non-abstract class that implements interface should be final.
17
 * - Except for Doctrine entities, they cannot be final.
18
 *
19
 * Inspiration:
20
 * - http://ocramius.github.io/blog/when-to-declare-classes-final/
21
 */
22
final class FinalInterfaceSniff implements PHP_CodeSniffer_Sniff
23
{
24
25
	/**
26
	 * @var PHP_CodeSniffer_File
27
	 */
28
	private $file;
29
30
	/**
31
	 * @var int
32
	 */
33
	private $position;
34
35
36
	/**
37
	 * {@inheritdoc}
38
	 */
39 1
	public function register()
40
	{
41 1
		return [T_CLASS];
42
	}
43
44
45
	/**
46
	 * {@inheritdoc}
47
	 */
48 1
	public function process(PHP_CodeSniffer_File $file, $position)
49
	{
50 1
		$this->file = $file;
51 1
		$this->position = $position;
52
53 1
		if ($this->implementsInterface() === FALSE) {
54
			return;
55
		}
56
57 1
		if ($this->isFinalOrAbstractClass()) {
58 1
			return;
59
		}
60
61 1
		if ($this->isDoctrineEntity()) {
62 1
			return;
63
		}
64
65 1
		$file->addError(
66 1
			'Non-abstract class that implements interface should be final.',
67
			$position
68
		);
69 1
	}
70
71
72
	/**
73
	 * @return bool
74
	 */
75 1
	private function implementsInterface()
76
	{
77 1
		return (bool) $this->file->findNext(T_IMPLEMENTS, $this->position);
78
	}
79
80
81
	/**
82
	 * @return bool
83
	 */
84 1
	private function isFinalOrAbstractClass()
85
	{
86 1
		$classProperties = $this->file->getClassProperties($this->position);
87 1
		return ($classProperties['is_abstract'] || $classProperties['is_final']);
88
	}
89
90
91
	/**
92
	 * @return bool
93
	 */
94 1
	private function isDoctrineEntity()
95
	{
96 1
		$docCommentPosition = $this->file->findPrevious(T_DOC_COMMENT_OPEN_TAG, $this->position);
97 1
		if ($docCommentPosition === FALSE) {
98 1
			return FALSE;
99
		}
100
101 1
		$seekPosition = $docCommentPosition;
102
103
		do {
104 1
			$docCommentTokenContent = $this->file->getTokens()[$docCommentPosition]['content'];
105 1
			if (strpos($docCommentTokenContent, 'Entity') !== FALSE) {
106 1
				return TRUE;
107
			}
108 1
			$seekPosition++;
109
110 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 108 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...
111
112
		return FALSE;
113
	}
114
115
}
116