Completed
Push — new-committers ( 29cb6f...bcba16 )
by Sam
12:18 queued 33s
created

SapphireTestReporter::startTest()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 24
rs 8.9713
cc 3
eloc 13
nc 3
nop 1
1
<?php
2
if(!class_exists('PHPUnit_Framework_TestResult', false)) require_once 'PHPUnit/Framework/TestResult.php';
3
if(!class_exists('PHPUnit_Framework_TestListener', false)) require_once 'PHPUnit/Framework/TestListener.php';
4
5
/**#@+
6
 * @var int
7
 */
8
/**
9
 * Failure test status constant
10
 */
11
define('TEST_FAILURE', -1);
12
/**
13
 * Error test status constant
14
 */
15
define('TEST_ERROR', 0);
16
/**
17
 * Success test status constant
18
 */
19
define('TEST_SUCCESS', 1);
20
/**
21
 * Incomplete test status constant
22
 */
23
define('TEST_INCOMPLETE', 2);
24
/**#@-*/
25
26
/**
27
 * Gathers details about PHPUnit2 test suites as they
28
 * are been executed. This does not actually format any output
29
 * but simply gathers extended information about the overall
30
 * results of all suites & their tests for use elsewhere.
31
 *
32
 * Changelog:
33
 *  0.6 First created [David Spurr]
34
 *  0.7 Added fix to getTestException provided [Glen Ogilvie]
35
 *
36
 * @package framework
37
 * @subpackage testing
38
 *
39
 * @version 0.7 2006-03-12
40
 * @author David Spurr
41
 */
42
class SapphireTestReporter implements PHPUnit_Framework_TestListener {
43
	/**
44
	 * Holds array of suites and total number of tests run
45
	 * @var array
46
	 */
47
	protected $suiteResults;
48
	/**
49
	 * Holds data of current suite that is been run
50
	 * @var array
51
	 */
52
	protected $currentSuite;
53
	/**
54
	 * Holds data of current test that is been run
55
	 * @var array
56
	 */
57
	protected $currentTest;
58
	/**
59
	 * Whether PEAR Benchmark_Timer is available for timing
60
	 * @var boolean
61
	 */
62
	protected $hasTimer;
63
	/**
64
	 * Holds the PEAR Benchmark_Timer object
65
	 * @var obj Benchmark_Timer
66
	 */
67
	protected $timer;
68
69
	protected $startTestTime;
70
71
	/**
72
	 * An array of all the test speeds
73
	 */
74
	protected $testSpeeds = array();
75
76
	/**
77
	 * Constructor, checks to see availability of PEAR Benchmark_Timer and
78
	 * sets up basic properties
79
	 *
80
	 * @access public
81
	 * @return void
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
82
	 */
83
	public function __construct() {
84
		@include_once 'Benchmark/Timer.php';
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
85
		if(class_exists('Benchmark_Timer')) {
86
			$this->timer = new Benchmark_Timer();
0 ignored issues
show
Documentation Bug introduced by
It seems like new \Benchmark_Timer() of type object<Benchmark_Timer> is incompatible with the declared type object<obj> of property $timer.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
87
			$this->hasTimer = true;
88
		} else {
89
			$this->hasTimer = false;
90
		}
91
92
		$this->suiteResults = array(
93
			'suites'      => array(),         // array of suites run
94
			'hasTimer'    => $this->hasTimer, // availability of PEAR Benchmark_Timer
95
			'totalTests'  => 0                // total number of tests run
96
		);
97
	}
98
99
	/**
100
	 * Returns the suite results
101
	 *
102
	 * @access public
103
	 * @return array Suite results
104
	 */
105
	public function getSuiteResults() {
106
		return $this->suiteResults;
107
	}
108
109
	/**
110
	 * Sets up the container for result details of the current test suite when
111
	 * each suite is first run
112
	 *
113
	 * @access public
114
	 * @param obj PHPUnit2_Framework_TestSuite, the suite that is been run
115
	 * @return void
116
	 */
117
	public function startTestSuite( PHPUnit_Framework_TestSuite $suite) {
118
		if(strlen($suite->getName())) {
119
			$this->endCurrentTestSuite();
120
		$this->currentSuite = array(
121
			'suite'      => $suite,  // the test suite
122
			'tests'      => array(), // the tests in the suite
123
			'errors'     => 0,       // number of tests with errors (including setup errors)
124
			'failures'   => 0,       // number of tests which failed
125
			'incomplete' => 0,       // number of tests that were not completed correctly
126
				'error'		 => null);	 // Any error encountered during setup of the test suite
127
	}
128
	}
129
130
	/**
131
	 * Sets up the container for result details of the current test when each
132
	 * test is first run
133
	 *
134
	 * @access public
135
	 * @param obj PHPUnit_Framework_Test, the test that is being run
136
	 * @return void
137
	 */
138
	public function startTest(PHPUnit_Framework_Test $test) {
139
		$this->startTestTime = microtime(true);
140
141
		if($test instanceof PHPUnit_Framework_TestCase) {
142
			$this->endCurrentTest();
143
			$this->currentTest = array(
144
				// the name of the test (without the suite name)
145
				'name'        => preg_replace('(\(.*\))', '', $test->toString()),
146
				// execution time of the test
147
				'timeElapsed' => 0,
148
				// status of the test execution
149
				'status'      => TEST_SUCCESS,
150
				// user message of test result
151
				'message'     => '',
152
				// original caught exception thrown by test upon failure/error
153
				'exception'   => NULL,
154
				// Stacktrace used for exception handling
155
				'trace'		  => NULL,
156
				// a unique ID for this test (used for identification purposes in results)
157
				'uid'         => md5(microtime())
158
			);
159
			if($this->hasTimer) $this->timer->start();
160
		}
161
	}
162
163
	/**
164
	 * Logs the specified status to the current test, or if no test is currently
165
	 * run, to the test suite.
166
	 * @param integer $status Status code
167
	 * @param string $message Message to log
168
	 * @param string $exception Exception body related to this message
169
	 * @param array $trace Stacktrace
170
	 */
171
	protected function addStatus($status, $message, $exception, $trace) {
172
		// Build status body to be saved
173
		$status = array(
174
			'status' => $status,
175
			'message' => $message,
176
			'exception' => $exception,
177
			'trace' => $trace
178
		);
179
180
		// Log either to current test or suite record
181
		if($this->currentTest) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->currentTest of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
182
			$this->currentTest = array_merge($this->currentTest, $status);
183
		} else {
184
			$this->currentSuite['error'] = $status;
185
		}
186
	}
187
188
	/**
189
	 * Adds the failure detail to the current test and increases the failure
190
	 * count for the current suite
191
	 *
192
	 * @access public
193
	 * @param obj PHPUnit_Framework_Test, current test that is being run
194
	 * @param obj PHPUnit_Framework_AssertationFailedError, PHPUnit error
195
	 * @return void
196
	 */
197
	public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) {
198
		$this->currentSuite['failures']++;
199
		$this->addStatus(TEST_FAILURE, $e->toString(), $this->getTestException($test, $e), $e->getTrace());
0 ignored issues
show
Documentation introduced by
$this->getTestException($test, $e) is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
200
	}
201
202
	/**
203
	 * Adds the error detail to the current test and increases the error
204
	 * count for the current suite
205
	 *
206
	 * @access public
207
	 * @param obj PHPUnit_Framework_Test, current test that is being run
208
	 * @param obj PHPUnit_Framework_AssertationFailedError, PHPUnit error
209
	 * @return void
210
	 */
211
	public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) {
212
		$this->currentSuite['errors']++;
213
		$this->addStatus(TEST_ERROR, $e->getMessage(), $this->getTestException($test, $e), $e->getTrace());
0 ignored issues
show
Documentation introduced by
$this->getTestException($test, $e) is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
214
	}
215
216
	/**
217
	 * Adds the test incomplete detail to the current test and increases the incomplete
218
	 * count for the current suite
219
	 *
220
	 * @access public
221
	 * @param obj PHPUnit_Framework_Test, current test that is being run
222
	 * @param obj PHPUnit_Framework_AssertationFailedError, PHPUnit error
223
	 * @return void
224
	 */
225
	public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) {
226
		$this->currentSuite['incomplete']++;
227
		$this->addStatus(TEST_INCOMPLETE, $e->toString(), $this->getTestException($test, $e), $e->getTrace());
0 ignored issues
show
Bug introduced by
The method toString() does not exist on Exception. Did you maybe mean __toString()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
Documentation introduced by
$this->getTestException($test, $e) is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
228
	}
229
230
	/**
231
	 * Not used
232
	 *
233
	 * @param PHPUnit_Framework_Test $test
234
	 * @param unknown_type $time
235
	 */
236
	public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) {
237
		// not implemented
238
	}
239
240
241
	/**
242
	 * Cleanly end the current test
243
	 */
244
	protected function endCurrentTest() {
245
		if(!$this->currentTest) return;
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->currentTest of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
246
247
		// Time the current test
248
		$testDuration = microtime(true) - $this->startTestTime;
249
		$this->testSpeeds[$this->currentSuite['suite']->getName() . '.' . $this->currentTest['name']] = $testDuration;
250
		if($this->hasTimer) {
251
			$this->timer->stop();
252
			$this->currentTest['timeElapsed'] = $this->timer->timeElapsed();
253
		}
254
255
		// Save and reset current state
256
		array_push($this->currentSuite['tests'], $this->currentTest);
257
		$this->currentTest = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $currentTest.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
258
	}
259
260
	/**
261
	 * Upon completion of a test, records the execution time (if available) and adds the test to
262
	 * the tests performed in the current suite.
263
	 *
264
	 * @access public
265
	 * @param obj PHPUnit_Framework_Test, current test that is being run
266
	 * @return void
267
	 */
268
	public function endTest( PHPUnit_Framework_Test $test, $time) {
269
		$this->endCurrentTest();
270
		if(method_exists($test, 'getActualOutput')) {
271
			$output = $test->getActualOutput();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface PHPUnit_Framework_Test as the method getActualOutput() does only exist in the following implementations of said interface: AbstractTest, BankAccountTest, BankAccountWithCustomExtensionTest, ChangeCurrentWorkingDirectoryTest, Composer\Installers\Test\AsgardInstallerTest, Composer\Installers\Test\CakePHPInstallerTest, Composer\Installers\Test\CraftInstallerTest, Composer\Installers\Test\DokuWikiInstallerTest, Composer\Installers\Test\GravInstallerTest, Composer\Installers\Test\InstallerTest, Composer\Installers\Test\MediaWikiInstallerTest, Composer\Installers\Test\OctoberInstallerTest, Composer\Installers\Test\PimcoreInstallerTest, Composer\Installers\Test\PiwikInstallerTest, Composer\Installers\Test\TestCase, ConcreteTest, ConcreteWithMyCustomExtensionTest, CountTest, DataProviderTest, DependencyFailureTest, DependencySuccessTest, EmptyTestCaseTest, Error, ExceptionInAssertPostConditionsTest, ExceptionInAssertPreConditionsTest, ExceptionInSetUpTest, ExceptionInTearDownTest, ExceptionInTest, ExceptionStackTest, ExceptionTest, Extensions_RepeatedTestTest, Failure, FailureTest, FatalTest, Foo_Bar_Issue684Test, Framework_AssertTest, Framework_Assert_FunctionsTest, Framework_ComparatorTest, Framework_ConstraintTest, Framework_Constraint_JsonMatchesTest, Framework_Constraint_Jso...rrorMessageProviderTest, Framework_SuiteTest, Framework_TestCaseTest, Framework_TestFailureTest, Framework_TestImplementorTest, Framework_TestListenerTest, IncompleteTest, InheritedTestCase, Issue1021Test, Issue1472Test, Issue244Test, Issue322Test, Issue433Test, Issue445Test, Issue498Test, Issue503Test, Issue523Test, Issue578Test, Issue581Test, Issue74Test, Issue765Test, MultiDependencyTest, My\Space\ExceptionNamespaceTest, NoArgTestCaseTest, NoTestCases, NotPublicTestCase, NotVoidTestCase, NothingTest, OneTest, OneTestCase, OutputTestCase, OverrideTestCase, PHPUnit_Framework_TestCase, PHPUnit_Framework_Warning, RequirementsTest, Runner_BaseTestRunnerTest, StackTest, Success, TemplateMethodsTest, ThrowExceptionTestCase, ThrowNoExceptionTestCase, TwoTest, Util_ClassTest, Util_ConfigurationTest, Util_DiffTest, Util_TestDox_NamePrettifierTest, Util_TestTest, Util_TypeTest, Util_XMLTest, WasRun.

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...
272
			if($output) echo "\nOutput:\n$output";
273
		}
274
	}
275
276
	/**
277
	 * Cleanly end the current test suite
278
	 */
279
	protected function endCurrentTestSuite() {
280
		if(!$this->currentSuite) return;
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->currentSuite of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
281
282
		// Ensure any current test is ended along with the current suite
283
		$this->endCurrentTest();
284
285
		// Save and reset current state
286
		array_push($this->suiteResults['suites'], $this->currentSuite);
287
		$this->currentSuite = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $currentSuite.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
288
	}
289
290
	/**
291
	 * Upon completion of a test suite adds the suite to the suties performed
292
	 *
293
	 * @access public
294
	 * @param obj PHPUnit_Framework_TestSuite, current suite that is being run
295
	 * @return void
296
	 */
297
	public function endTestSuite( PHPUnit_Framework_TestSuite $suite) {
298
		if(strlen($suite->getName())) {
299
			$this->endCurrentTestSuite();
300
		}
301
	}
302
303
	/**
304
	 * Risky test.
305
	 *
306
	 * @param PHPUnit_Framework_Test $test
307
	 * @param Exception              $e
308
	 * @param float                  $time
309
	 * @since  Method available since Release 3.8.0
310
	 */
311
	public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) {
0 ignored issues
show
Unused Code introduced by
The parameter $test is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $e is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $time is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
312
		// Stub out to support PHPUnit 3.8
313
	}
314
315
	/**
316
	 * Trys to get the original exception thrown by the test on failure/error
317
	 * to enable us to give a bit more detail about the failure/error
318
	 *
319
	 * @access private
320
	 * @param obj PHPUnit_Framework_Test, current test that is being run
321
	 * @param obj PHPUnit_Framework_AssertationFailedError, PHPUnit error
322
	 * @return array
323
	 */
324
	private function getTestException(PHPUnit_Framework_Test $test, Exception $e) {
325
		// get the name of the testFile from the test
326
		$testName = preg_replace('/(.*)\((.*[^)])\)/', '\\2', $test->toString());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface PHPUnit_Framework_Test as the method toString() does only exist in the following implementations of said interface: AbstractTest, BankAccountTest, BankAccountWithCustomExtensionTest, ChangeCurrentWorkingDirectoryTest, Composer\Installers\Test\AsgardInstallerTest, Composer\Installers\Test\CakePHPInstallerTest, Composer\Installers\Test\CraftInstallerTest, Composer\Installers\Test\DokuWikiInstallerTest, Composer\Installers\Test\GravInstallerTest, Composer\Installers\Test\InstallerTest, Composer\Installers\Test\MediaWikiInstallerTest, Composer\Installers\Test\OctoberInstallerTest, Composer\Installers\Test\PimcoreInstallerTest, Composer\Installers\Test\PiwikInstallerTest, Composer\Installers\Test\TestCase, ConcreteTest, ConcreteWithMyCustomExtensionTest, CountTest, DataProviderTest, DependencyFailureTest, DependencySuccessTest, EmptyTestCaseTest, Error, ExceptionInAssertPostConditionsTest, ExceptionInAssertPreConditionsTest, ExceptionInSetUpTest, ExceptionInTearDownTest, ExceptionInTest, ExceptionStackTest, ExceptionTest, Extensions_RepeatedTestTest, Failure, FailureTest, FatalTest, Foo_Bar_Issue684Test, Framework_AssertTest, Framework_Assert_FunctionsTest, Framework_ComparatorTest, Framework_ConstraintTest, Framework_Constraint_JsonMatchesTest, Framework_Constraint_Jso...rrorMessageProviderTest, Framework_SuiteTest, Framework_TestCaseTest, Framework_TestFailureTest, Framework_TestImplementorTest, Framework_TestListenerTest, IncompleteTest, InheritedTestCase, Issue1021Test, Issue1472Test, Issue244Test, Issue322Test, Issue433Test, Issue445Test, Issue498Test, Issue503Test, Issue523Test, Issue578Test, Issue581Test, Issue74Test, Issue765Test, MultiDependencyTest, My\Space\ExceptionNamespaceTest, NoArgTestCaseTest, NoTestCases, NotPublicTestCase, NotVoidTestCase, NothingTest, OneTest, OneTestCase, OutputTestCase, OverrideTestCase, PHPUnit_Extensions_GroupTestSuite, PHPUnit_Extensions_PhptTestCase, PHPUnit_Extensions_PhptTestSuite, PHPUnit_Extensions_RepeatedTest, PHPUnit_Extensions_TestDecorator, PHPUnit_Framework_TestCase, PHPUnit_Framework_TestSuite, PHPUnit_Framework_TestSuite_DataProvider, PHPUnit_Framework_Warning, RequirementsTest, Runner_BaseTestRunnerTest, SapphireTestSuite, StackTest, Success, TemplateMethodsTest, ThrowExceptionTestCase, ThrowNoExceptionTestCase, TwoTest, Util_ClassTest, Util_ConfigurationTest, Util_DiffTest, Util_TestDox_NamePrettifierTest, Util_TestTest, Util_TypeTest, Util_XMLTest, WasRun.

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...
327
		$trace = $e->getTrace();
328
		// loop through the exception trace to find the original exception
329
		for($i = 0; $i < count($trace); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
330
331 View Code Duplication
			if(array_key_exists('file', $trace[$i])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
332
				if(stristr($trace[$i]['file'], $testName.'.php') != false) return $trace[$i];
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing stristr($trace[$i]['file'], $testName . '.php') of type string to the boolean false. If you are specifically checking for a non-empty string, consider using the more explicit !== '' instead.
Loading history...
333
			}
334 View Code Duplication
			if(array_key_exists('file:protected', $trace[$i])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
335
				if(stristr($trace[$i]['file:protected'], $testName.'.php') != false) return $trace[$i];
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing stristr($trace[$i]['file...'], $testName . '.php') of type string to the boolean false. If you are specifically checking for a non-empty string, consider using the more explicit !== '' instead.
Loading history...
336
			}
337
		}
338
	}
339
340
	/**
341
	 * Writes a status message to the output stream in a user readable HTML format
342
	 * @param string $name Name of the object that generated the error
343
	 * @param string $message Message of the error
344
	 * @param array $trace Stacktrace
345
	 */
346
	protected function writeResultError($name, $message, $trace) {
347
		echo "<div class=\"failure\"><h2 class=\"test-case\">&otimes; ". $this->testNameToPhrase($name) ."</h2>";
348
		echo "<pre>".htmlentities($message, ENT_COMPAT, 'UTF-8')."</pre>";
349
		echo SS_Backtrace::get_rendered_backtrace($trace);
0 ignored issues
show
Documentation introduced by
$trace is of type array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
350
		echo "</div>";
351
	}
352
353
	/**
354
	 * Display error bar if it exists
355
	 */
356
	public function writeResults() {
357
		$passCount = 0;
358
		$failCount = 0;
359
		$testCount = 0;
360
		$incompleteCount = 0;
361
		$errorCount = 0; // Includes both suite and test level errors
362
363
		// Ensure that the current suite is cleanly ended.
364
		// A suite may not end correctly if there was an error during setUp
365
		$this->endCurrentTestSuite();
366
367
		foreach($this->suiteResults['suites'] as $suite) {
368
369
			// Report suite error. In the case of fatal non-success messages
370
			// These should be reported as errors. Failure/Success relate only
371
			// to individual tests directly
372
			if($suite['error']) {
373
				$errorCount++;
374
				$this->writeResultError(
375
					$suite['suite']->getName(),
376
					$suite['error']['message'],
377
					$suite['error']['trace']
378
				);
379
			}
380
381
			// Run through all tests in this suite
382
			foreach($suite['tests'] as $test) {
383
				$testCount++;
384
				switch($test['status']) {
385
					case TEST_ERROR: $errorCount++; break;
386
					case TEST_INCOMPLETE: $incompleteCount++; break;
387
					case TEST_SUCCESS: $passCount++; break;
388
					case TEST_FAILURE: $failCount++; break;
389
				}
390
391
				// Report test error
392
				if ($test['status'] != TEST_SUCCESS) {
393
					$this->writeResultError(
394
						$test['name'],
395
						$test['message'],
396
						$test['trace']
397
					);
398
				}
399
			}
400
		}
401
		$result = ($failCount || $errorCount) ? 'fail' : 'pass';
402
		echo "<div class=\"status $result\">";
403
		echo "<h2><span>$testCount</span> tests run: <span>$passCount</span> passes, <span>$failCount</span> failures,"
404
			. " and <span>$incompleteCount</span> incomplete with <span>$errorCount</span> errors</h2>";
405
		echo "</div>";
406
407
	}
408
409
	protected function testNameToPhrase($name) {
410
		return ucfirst(preg_replace("/([a-z])([A-Z])/", "$1 $2", $name));
411
	}
412
}
413
414