Completed
Push — authenticator-refactor ( 62753b...d89bd1 )
by Damian
05:57
created

SapphireTest::logOut()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace SilverStripe\Dev;
4
5
use SilverStripe\CMS\Controllers\RootURLController;
6
use SilverStripe\Control\Cookie;
7
use SilverStripe\Control\Email\Email;
8
use SilverStripe\Control\Email\Mailer;
9
use SilverStripe\Control\Session;
10
use SilverStripe\Control\Controller;
11
use SilverStripe\Control\Director;
12
use SilverStripe\Control\Tests\FakeController;
13
use SilverStripe\Core\Config\Config;
14
use SilverStripe\Core\ClassInfo;
15
use SilverStripe\Core\Config\ConfigLoader;
16
use SilverStripe\Core\Config\CoreConfigFactory;
17
use SilverStripe\Core\Config\DefaultConfig;
18
use SilverStripe\Core\Config\Middleware\ExtensionMiddleware;
19
use SilverStripe\Core\Extension;
20
use SilverStripe\Core\Flushable;
21
use SilverStripe\Core\Injector\Injector;
22
use SilverStripe\Core\Manifest\ClassManifest;
23
use SilverStripe\Core\Manifest\ClassLoader;
24
use SilverStripe\Core\Resettable;
25
use SilverStripe\i18n\i18n;
26
use SilverStripe\ORM\DataExtension;
27
use SilverStripe\ORM\SS_List;
28
use SilverStripe\Security\IdentityStore;
29
use SilverStripe\Versioned\Versioned;
30
use SilverStripe\ORM\DataObject;
31
use SilverStripe\ORM\DataModel;
32
use SilverStripe\ORM\FieldType\DBDatetime;
33
use SilverStripe\ORM\FieldType\DBField;
34
use SilverStripe\ORM\DB;
35
use SilverStripe\Security\Member;
36
use SilverStripe\Security\Security;
37
use SilverStripe\Security\Group;
38
use SilverStripe\Security\Permission;
39
use SilverStripe\View\Requirements;
40
use SilverStripe\View\SSViewer;
41
use SilverStripe\View\ThemeResourceLoader;
42
use SilverStripe\View\ThemeManifest;
43
use PHPUnit_Framework_TestCase;
44
use Translatable;
45
use LogicException;
46
use Exception;
47
48
/**
49
 * Test case class for the Sapphire framework.
50
 * Sapphire unit testing is based on PHPUnit, but provides a number of hooks into our data model that make it easier
51
 * to work with.
52
 */
53
class SapphireTest extends PHPUnit_Framework_TestCase
54
{
55
56
    /** @config */
57
    private static $dependencies = array(
58
        'fixtureFactory' => '%$FixtureFactory',
59
    );
60
61
    /**
62
     * Path to fixture data for this test run.
63
     * If passed as an array, multiple fixture files will be loaded.
64
     * Please note that you won't be able to refer with "=>" notation
65
     * between the fixtures, they act independent of each other.
66
     *
67
     * @var string|array
68
     */
69
    protected static $fixture_file = null;
70
71
    /**
72
     * @var FixtureFactory
73
     */
74
    protected $fixtureFactory;
75
76
    /**
77
     * @var Boolean If set to TRUE, this will force a test database to be generated
78
     * in {@link setUp()}. Note that this flag is overruled by the presence of a
79
     * {@link $fixture_file}, which always forces a database build.
80
     */
81
    protected $usesDatabase = null;
82
    protected $originalMemberPasswordValidator;
83
    protected $originalRequirements;
84
    protected $originalIsRunningTest;
85
    protected $originalNestedURLsState;
86
    protected $originalMemoryLimit;
87
88
    /**
89
     * @var TestMailer
90
     */
91
    protected $mailer;
92
93
    /**
94
     * Pointer to the manifest that isn't a test manifest
95
     */
96
    protected static $regular_manifest;
97
98
    /**
99
     * @var boolean
100
     */
101
    protected static $is_running_test = false;
102
103
    /**
104
     * @var ClassManifest
105
     */
106
    protected static $test_class_manifest;
107
108
    /**
109
     * By default, setUp() does not require default records. Pass
110
     * class names in here, and the require/augment default records
111
     * function will be called on them.
112
     */
113
    protected $requireDefaultRecordsFrom = array();
114
115
    /**
116
     * A list of extensions that can't be applied during the execution of this run.  If they are
117
     * applied, they will be temporarily removed and a database migration called.
118
     *
119
     * The keys of the are the classes that the extensions can't be applied the extensions to, and
120
     * the values are an array of illegal extensions on that class.
121
     *
122
     * Set a class to `*` to remove all extensions (unadvised)
123
     */
124
    protected static $illegal_extensions = [];
125
126
    /**
127
     * A list of extensions that must be applied during the execution of this run.  If they are
128
     * not applied, they will be temporarily added and a database migration called.
129
     *
130
     * The keys of the are the classes to apply the extensions to, and the values are an array
131
     * of required extensions on that class.
132
     *
133
     * Example:
134
     * <code>
135
     * array("MyTreeDataObject" => array("Versioned", "Hierarchy"))
136
     * </code>
137
     */
138
    protected static $required_extensions = [];
139
140
    /**
141
     * By default, the test database won't contain any DataObjects that have the interface TestOnly.
142
     * This variable lets you define additional TestOnly DataObjects to set up for this test.
143
     * Set it to an array of DataObject subclass names.
144
     */
145
    protected static $extra_dataobjects = [];
146
147
    /**
148
     * List of class names of {@see Controller} objects to register routes for
149
     * Controllers must implement Link() method
150
     *
151
     * @var array
152
     */
153
    protected static $extra_controllers = [];
154
155
    /**
156
     * We need to disabling backing up of globals to avoid overriding
157
     * the few globals SilverStripe relies on, like $lang for the i18n subsystem.
158
     *
159
     * @see http://sebastian-bergmann.de/archives/797-Global-Variables-and-PHPUnit.html
160
     */
161
    protected $backupGlobals = false;
162
163
    /**
164
     * Helper arrays for illegal_extensions/required_extensions code
165
     */
166
    private static $extensions_to_reapply = [];
167
168
    private static $extensions_to_remove = [];
169
170
    /**
171
     * Check flushables on setupBeforeClass()
172
     *
173
     * @var bool
174
     */
175
    protected static $flushedFlushables = false;
176
177
    /**
178
     * Determines if unit tests are currently run, flag set during test bootstrap.
179
     * This is used as a cheap replacement for fully mockable state
180
     * in certain contiditions (e.g. access checks).
181
     * Caution: When set to FALSE, certain controllers might bypass
182
     * access checks, so this is a very security sensitive setting.
183
     *
184
     * @return boolean
185
     */
186
    public static function is_running_test()
187
    {
188
        return self::$is_running_test;
189
    }
190
191
    public static function set_is_running_test($bool)
192
    {
193
        self::$is_running_test = $bool;
194
    }
195
196
    /**
197
     * Set the manifest to be used to look up test classes by helper functions
198
     *
199
     * @param ClassManifest $manifest
200
     */
201
    public static function set_test_class_manifest($manifest)
202
    {
203
        self::$test_class_manifest = $manifest;
204
    }
205
206
    /**
207
     * Return the manifest being used to look up test classes by helper functions
208
     *
209
     * @return ClassManifest
210
     */
211
    public static function get_test_class_manifest()
212
    {
213
        return self::$test_class_manifest;
214
    }
215
216
    /**
217
     * @return String
218
     */
219
    public static function get_fixture_file()
220
    {
221
        return static::$fixture_file;
222
    }
223
224
    protected $model;
225
226
    /**
227
     * State of Versioned before this test is run
228
     *
229
     * @var string
230
     */
231
    protected $originalReadingMode = null;
232
233
    protected $originalEnv = null;
234
235
    protected function setUp()
236
    {
237
        //nest config and injector for each test so they are effectively sandboxed per test
238
        Config::nest();
239
        Injector::nest();
240
241
        $this->originalEnv = Director::get_environment_type();
242
        if (class_exists(Versioned::class)) {
243
            $this->originalReadingMode = Versioned::get_reading_mode();
244
        }
245
246
        // We cannot run the tests on this abstract class.
247
        if (static::class == __CLASS__) {
248
            $this->markTestSkipped(sprintf('Skipping %s ', static::class));
249
            return;
250
        }
251
252
        // Mark test as being run
253
        $this->originalIsRunningTest = self::$is_running_test;
254
        self::$is_running_test = true;
255
256
        // i18n needs to be set to the defaults or tests fail
257
        i18n::set_locale(i18n::config()->uninherited('default_locale'));
258
259
        // Set default timezone consistently to avoid NZ-specific dependencies
260
        date_default_timezone_set('UTC');
261
262
        // Remove password validation
263
        $this->originalMemberPasswordValidator = Member::password_validator();
264
        $this->originalRequirements = Requirements::backend();
265
        Member::set_password_validator(null);
0 ignored issues
show
Documentation introduced by
null is of type null, but the function expects a object<SilverStripe\Security\PasswordValidator>.

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...
266
        Cookie::config()->update('report_errors', false);
267
        if (class_exists(RootURLController::class)) {
268
            RootURLController::reset();
269
        }
270
271
        // Reset all resettables
272
        /** @var Resettable $resettable */
273
        foreach (ClassInfo::implementorsOf(Resettable::class) as $resettable) {
274
            $resettable::reset();
275
        }
276
277
        if (Controller::has_curr()) {
278
            Controller::curr()->setSession(Session::create(array()));
279
        }
280
        Security::clear_database_is_ready();
281
282
        // Set up test routes
283
        $this->setUpRoutes();
284
285
        $fixtureFiles = $this->getFixturePaths();
286
287
        // Todo: this could be a special test model
288
        $this->model = DataModel::inst();
289
290
        // Set up fixture
291
        if ($fixtureFiles || $this->usesDatabase) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fixtureFiles 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...
292
            if (!self::using_temp_db()) {
293
                self::create_temp_db();
294
            }
295
296
            DataObject::singleton()->flushCache();
297
298
            self::empty_temp_db();
299
300
            foreach ($this->requireDefaultRecordsFrom as $className) {
301
                $instance = singleton($className);
302
                if (method_exists($instance, 'requireDefaultRecords')) {
303
                    $instance->requireDefaultRecords();
304
                }
305
                if (method_exists($instance, 'augmentDefaultRecords')) {
306
                    $instance->augmentDefaultRecords();
307
                }
308
            }
309
310
            foreach ($fixtureFiles as $fixtureFilePath) {
311
                $fixture = YamlFixture::create($fixtureFilePath);
312
                $fixture->writeInto($this->getFixtureFactory());
313
            }
314
315
            $this->logInWithPermission("ADMIN");
316
        }
317
318
        // Preserve memory settings
319
        $this->originalMemoryLimit = ini_get('memory_limit');
320
321
        // turn off template debugging
322
        SSViewer::config()->update('source_file_comments', false);
323
324
        // Clear requirements
325
        Requirements::clear();
326
327
        // Set up the test mailer
328
        $this->mailer = new TestMailer();
329
        Injector::inst()->registerService($this->mailer, Mailer::class);
330
        Email::config()->remove('send_all_emails_to');
331
        Email::config()->remove('send_all_emails_from');
332
        Email::config()->remove('cc_all_emails_to');
333
        Email::config()->remove('bcc_all_emails_to');
334
    }
335
336
    /**
337
     * Called once per test case ({@link SapphireTest} subclass).
338
     * This is different to {@link setUp()}, which gets called once
339
     * per method. Useful to initialize expensive operations which
340
     * don't change state for any called method inside the test,
341
     * e.g. dynamically adding an extension. See {@link teardownAfterClass()}
342
     * for tearing down the state again.
343
     */
344
    public static function setUpBeforeClass()
345
    {
346
        static::start();
347
348
        //nest config and injector for each suite so they are effectively sandboxed
349
        Config::nest();
350
        Injector::nest();
351
        $isAltered = false;
352
353
        if (!Director::isDev()) {
354
            user_error('Tests can only run in "dev" mode', E_USER_ERROR);
355
        }
356
357
        // Remove any illegal extensions that are present
358
        foreach (static::$illegal_extensions as $class => $extensions) {
359
            if (!class_exists($class)) {
360
                continue;
361
            }
362
            if ($extensions === '*') {
363
                $extensions = $class::get_extensions();
364
            }
365
            foreach ($extensions as $extension) {
366
                if (!class_exists($extension) || !$class::has_extension($extension)) {
367
                    continue;
368
                }
369
                if (!isset(self::$extensions_to_reapply[$class])) {
370
                    self::$extensions_to_reapply[$class] = array();
371
                }
372
                self::$extensions_to_reapply[$class][] = $extension;
373
                $class::remove_extension($extension);
374
                $isAltered = true;
375
            }
376
        }
377
378
        // Add any required extensions that aren't present
379
        foreach (static::$required_extensions as $class => $extensions) {
380
            if (!class_exists($class)) {
381
                $self = static::class;
382
                throw new LogicException("Test {$self} requires class {$class} which doesn't exist");
383
            }
384
            self::$extensions_to_remove[$class] = array();
385
            foreach ($extensions as $extension) {
386
                $extensionClass = Extension::get_classname_without_arguments($extension);
387
                if (!class_exists($extensionClass)) {
388
                    $self = static::class;
389
                    throw new LogicException("Test {$self} requires extension {$extension} which doesn't exist");
390
                }
391
                if (!$class::has_extension($extension)) {
392
                    if (!isset(self::$extensions_to_remove[$class])) {
393
                        self::$extensions_to_reapply[$class] = array();
394
                    }
395
                    self::$extensions_to_remove[$class][] = $extension;
396
                    $class::add_extension($extension);
397
                    $isAltered = true;
398
                }
399
            }
400
        }
401
402
        // If we have made changes to the extensions present, then migrate the database schema.
403
        if ($isAltered || self::$extensions_to_reapply || self::$extensions_to_remove || static::getExtraDataObjects()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression self::$extensions_to_reapply 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...
Bug Best Practice introduced by
The expression self::$extensions_to_remove 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...
404
            DataObject::reset();
405
            if (!self::using_temp_db()) {
406
                self::create_temp_db();
407
            }
408
            static::resetDBSchema(true);
409
        }
410
        // clear singletons, they're caching old extension info
411
        // which is used in DatabaseAdmin->doBuild()
412
        Injector::inst()->unregisterAllObjects();
413
414
        // Set default timezone consistently to avoid NZ-specific dependencies
415
        date_default_timezone_set('UTC');
416
417
        // Flush all flushable records
418
        $flush = !empty($_GET['flush']);
419
        if (!self::$flushedFlushables && $flush) {
420
            self::$flushedFlushables = true;
421
            foreach (ClassInfo::implementorsOf(Flushable::class) as $class) {
422
                $class::flush();
423
            }
424
        }
425
    }
426
427
    /**
428
     * tearDown method that's called once per test class rather once per test method.
429
     */
430
    public static function tearDownAfterClass()
431
    {
432
        // If we have made changes to the extensions present, then migrate the database schema.
433
        if (self::$extensions_to_reapply || self::$extensions_to_remove) {
0 ignored issues
show
Bug Best Practice introduced by
The expression self::$extensions_to_reapply 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...
Bug Best Practice introduced by
The expression self::$extensions_to_remove 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...
434
            // @todo: This isn't strictly necessary to restore extensions, but only to ensure that
435
            // Object::$extra_methods is properly flushed. This should be replaced with a simple
436
            // flush mechanism for each $class.
437
            //
438
            // Remove extensions added for testing
439
            foreach (self::$extensions_to_remove as $class => $extensions) {
440
                foreach ($extensions as $extension) {
441
                    $class::remove_extension($extension);
442
                }
443
            }
444
445
            // Reapply ones removed
446
            foreach (self::$extensions_to_reapply as $class => $extensions) {
447
                foreach ($extensions as $extension) {
448
                    $class::add_extension($extension);
449
                }
450
            }
451
        }
452
453
        //unnest injector / config now that the test suite is over
454
        // this will reset all the extensions on the object too (see setUpBeforeClass)
455
        Injector::unnest();
456
        Config::unnest();
457
458
        $extraDataObjects = static::getExtraDataObjects();
459
        if (!empty(self::$extensions_to_reapply) || !empty(self::$extensions_to_remove) || !empty($extraDataObjects)) {
460
            static::resetDBSchema();
461
        }
462
    }
463
464
    /**
465
     * @return FixtureFactory
466
     */
467
    public function getFixtureFactory()
468
    {
469
        if (!$this->fixtureFactory) {
470
            $this->fixtureFactory = Injector::inst()->create(FixtureFactory::class);
471
        }
472
        return $this->fixtureFactory;
473
    }
474
475
    public function setFixtureFactory(FixtureFactory $factory)
476
    {
477
        $this->fixtureFactory = $factory;
478
        return $this;
479
    }
480
481
    /**
482
     * Get the ID of an object from the fixture.
483
     *
484
     * @param string $className The data class or table name, as specified in your fixture file.  Parent classes won't work
485
     * @param string $identifier The identifier string, as provided in your fixture file
486
     * @return int
487
     */
488 View Code Duplication
    protected function idFromFixture($className, $identifier)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
489
    {
490
        $id = $this->getFixtureFactory()->getId($className, $identifier);
491
492
        if (!$id) {
493
            user_error(sprintf(
494
                "Couldn't find object '%s' (class: %s)",
495
                $identifier,
496
                $className
497
            ), E_USER_ERROR);
498
        }
499
500
        return $id;
501
    }
502
503
    /**
504
     * Return all of the IDs in the fixture of a particular class name.
505
     * Will collate all IDs form all fixtures if multiple fixtures are provided.
506
     *
507
     * @param string $className The data class or table name, as specified in your fixture file
508
     * @return array A map of fixture-identifier => object-id
509
     */
510
    protected function allFixtureIDs($className)
511
    {
512
        return $this->getFixtureFactory()->getIds($className);
513
    }
514
515
    /**
516
     * Get an object from the fixture.
517
     *
518
     * @param string $className The data class or table name, as specified in your fixture file. Parent classes won't work
519
     * @param string $identifier The identifier string, as provided in your fixture file
520
     *
521
     * @return DataObject
522
     */
523 View Code Duplication
    protected function objFromFixture($className, $identifier)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
524
    {
525
        $obj = $this->getFixtureFactory()->get($className, $identifier);
526
527
        if (!$obj) {
528
            user_error(sprintf(
529
                "Couldn't find object '%s' (class: %s)",
530
                $identifier,
531
                $className
532
            ), E_USER_ERROR);
533
        }
534
535
        return $obj;
536
    }
537
538
    /**
539
     * Load a YAML fixture file into the database.
540
     * Once loaded, you can use idFromFixture() and objFromFixture() to get items from the fixture.
541
     * Doesn't clear existing fixtures.
542
     *
543
     * @param string $fixtureFile The location of the .yml fixture file, relative to the site base dir
544
     */
545
    public function loadFixture($fixtureFile)
546
    {
547
        $fixture = Injector::inst()->create(YamlFixture::class, $fixtureFile);
548
        $fixture->writeInto($this->getFixtureFactory());
549
    }
550
551
    /**
552
     * Clear all fixtures which were previously loaded through
553
     * {@link loadFixture()}
554
     */
555
    public function clearFixtures()
556
    {
557
        $this->getFixtureFactory()->clear();
558
    }
559
560
    /**
561
     * Useful for writing unit tests without hardcoding folder structures.
562
     *
563
     * @return String Absolute path to current class.
564
     */
565
    protected function getCurrentAbsolutePath()
566
    {
567
        $filename = self::$test_class_manifest->getItemPath(static::class);
568
        if (!$filename) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $filename of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
569
            throw new LogicException("getItemPath returned null for " . static::class);
570
        }
571
        return dirname($filename);
572
    }
573
574
    /**
575
     * @return String File path relative to webroot
576
     */
577
    protected function getCurrentRelativePath()
578
    {
579
        $base = Director::baseFolder();
580
        $path = $this->getCurrentAbsolutePath();
581
        if (substr($path, 0, strlen($base)) == $base) {
582
            $path = preg_replace('/^\/*/', '', substr($path, strlen($base)));
583
        }
584
        return $path;
585
    }
586
587
    protected function tearDown()
588
    {
589
        // Preserve memory settings
590
        ini_set('memory_limit', ($this->originalMemoryLimit) ? $this->originalMemoryLimit : -1);
591
592
        // Restore email configuration
593
        $this->mailer = null;
594
595
        // Restore password validation
596
        if ($this->originalMemberPasswordValidator) {
597
            Member::set_password_validator($this->originalMemberPasswordValidator);
598
        }
599
600
        // Restore requirements
601
        if ($this->originalRequirements) {
602
            Requirements::set_backend($this->originalRequirements);
603
        }
604
605
        // Mark test as no longer being run - we use originalIsRunningTest to allow for nested SapphireTest calls
606
        self::$is_running_test = $this->originalIsRunningTest;
607
        $this->originalIsRunningTest = null;
608
609
        // Reset mocked datetime
610
        DBDatetime::clear_mock_now();
611
612
        // Stop the redirection that might have been requested in the test.
613
        // Note: Ideally a clean Controller should be created for each test.
614
        // Now all tests executed in a batch share the same controller.
615
        $controller = Controller::has_curr() ? Controller::curr() : null;
616
        if ($controller && ($response = $controller->getResponse()) && $response->getHeader('Location')) {
617
            $response->setStatusCode(200);
618
            $response->removeHeader('Location');
619
        }
620
621
        Director::set_environment_type($this->originalEnv);
0 ignored issues
show
Bug introduced by
It seems like $this->originalEnv can also be of type boolean; however, SilverStripe\Control\Dir...:set_environment_type() does only seem to accept string, 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...
622
        if (class_exists(Versioned::class)) {
623
            Versioned::set_reading_mode($this->originalReadingMode);
624
        }
625
626
        //unnest injector / config now that tests are over
627
        Injector::unnest();
628
        Config::unnest();
629
    }
630
631 View Code Duplication
    public static function assertContains(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
632
        $needle,
633
        $haystack,
634
        $message = '',
635
        $ignoreCase = false,
636
        $checkForObjectIdentity = true,
637
        $checkForNonObjectIdentity = false
638
    ) {
639
        if ($haystack instanceof DBField) {
640
            $haystack = (string)$haystack;
641
        }
642
        parent::assertContains($needle, $haystack, $message, $ignoreCase, $checkForObjectIdentity, $checkForNonObjectIdentity);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class PHPUnit_Framework_TestCase as the method assertContains() does only exist in the following sub-classes of PHPUnit_Framework_TestCase: AbstractTest, AssertionExampleTest, BankAccountTest, BankAccountWithCustomExtensionTest, BeforeAndAfterTest, BeforeClassAndAfterClassTest, BeforeClassWithOnlyDataProviderTest, ChangeCurrentWorkingDirectoryTest, ClonedDependencyTest, ConcreteTest, ConcreteWithMyCustomExtensionTest, CountTest, CoverageClassExtendedTest, CoverageClassTest, CoverageFunctionParenthesesTest, CoverageFunctionParenthesesWhitespaceTest, CoverageFunctionTest, CoverageMethodOneLineAnnotationTest, CoverageMethodParenthesesTest, CoverageMethodParenthesesWhitespaceTest, CoverageMethodTest, CoverageNamespacedFunctionTest, CoverageNoneTest, CoverageNotPrivateTest, CoverageNotProtectedTest, CoverageNotPublicTest, CoverageNothingTest, CoveragePrivateTest, CoverageProtectedTest, CoveragePublicTest, DataProviderDebugTest, DataProviderDependencyTest, DataProviderFilterTest, DataProviderIncompleteTest, DataProviderSkippedTest, DataProviderTest, DataProviderTestDoxTest, DependencyFailureTest, DependencySuccessTest, EmptyTestCaseTest, ExceptionInAssertPostConditionsTest, ExceptionInAssertPreConditionsTest, ExceptionInSetUpTest, ExceptionInTearDownTest, ExceptionInTest, ExceptionMessageRegExpTest, ExceptionMessageTest, ExceptionStackTest, ExceptionTest, Extensions_PhptTestCaseTest, Extensions_RepeatedTestTest, Failure, FailureTest, FatalTest, Foo_Bar_Issue684Test, Framework_AssertTest, Framework_BaseTestListenerTest, Framework_ConstraintTest, Framework_Constraint_IsJsonTest, Framework_Constraint_JsonMatchesTest, Framework_Constraint_Jso...rrorMessageProviderTest, Framework_SuiteTest, Framework_TestCaseTest, Framework_TestFailureTest, Framework_TestImplementorTest, Framework_TestListenerTest, IgnoreCodeCoverageClassTest, IncompleteTest, InheritanceA, InheritanceB, InheritedTestCase, IniTest, IsolationTest, Issue1021Test, Issue1149Test, Issue1216Test, Issue1265Test, Issue1330Test, Issue1335Test, Issue1337Test, Issue1348Test, Issue1351Test, Issue1374Test, Issue1437Test, Issue1468Test, Issue1471Test, Issue1472Test, Issue1570Test, Issue2137Test, Issue2145Test, Issue2158Test, Issue2366Test, Issue2380Test, Issue2382Test, Issue2435Test, Issue244Test, Issue322Test, Issue433Test, Issue445Test, Issue498Test, Issue503Test, Issue523Test, Issue578Test, Issue581Test, Issue74Test, Issue765Test, Issue797Test, MJS\TopSort\Tests\GroupedSortTest, MJS\TopSort\Tests\SimpleSortTest, MJS\TopSort\Tests\TestCase, Monolog\ErrorHandlerTest, Monolog\Formatter\ChromePHPFormatterTest, Monolog\Formatter\ElasticaFormatterTest, Monolog\Formatter\FlowdockFormatterTest, Monolog\Formatter\FluentdFormatterTest, Monolog\Formatter\GelfMessageFormatterTest, Monolog\Formatter\JsonFormatterTest, Monolog\Formatter\LineFormatterTest, Monolog\Formatter\LogglyFormatterTest, Monolog\Formatter\LogstashFormatterTest, Monolog\Formatter\MongoDBFormatterTest, Monolog\Formatter\NormalizerFormatterTest, Monolog\Formatter\ScalarFormatterTest, Monolog\Formatter\WildfireFormatterTest, Monolog\Handler\AbstractHandlerTest, Monolog\Handler\AbstractProcessingHandlerTest, Monolog\Handler\AmqpHandlerTest, Monolog\Handler\BrowserConsoleHandlerTest, Monolog\Handler\BufferHandlerTest, Monolog\Handler\ChromePHPHandlerTest, Monolog\Handler\CouchDBHandlerTest, Monolog\Handler\DeduplicationHandlerTest, Monolog\Handler\DoctrineCouchDBHandlerTest, Monolog\Handler\DynamoDbHandlerTest, Monolog\Handler\ElasticSearchHandlerTest, Monolog\Handler\ErrorLogHandlerTest, Monolog\Handler\FilterHandlerTest, Monolog\Handler\FingersCrossedHandlerTest, Monolog\Handler\FirePHPHandlerTest, Monolog\Handler\FleepHookHandlerTest, Monolog\Handler\FlowdockHandlerTest, Monolog\Handler\GelfHandlerLegacyTest, Monolog\Handler\GelfHandlerTest, Monolog\Handler\GroupHandlerTest, Monolog\Handler\HandlerWrapperTest, Monolog\Handler\HipChatHandlerTest, Monolog\Handler\LogEntriesHandlerTest, Monolog\Handler\MailHandlerTest, Monolog\Handler\MongoDBHandlerTest, Monolog\Handler\NativeMailerHandlerTest, Monolog\Handler\NewRelicHandlerTest, Monolog\Handler\NullHandlerTest, Monolog\Handler\PHPConsoleHandlerTest, Monolog\Handler\PsrHandlerTest, Monolog\Handler\PushoverHandlerTest, Monolog\Handler\RavenHandlerTest, Monolog\Handler\RedisHandlerTest, Monolog\Handler\RollbarHandlerTest, Monolog\Handler\RotatingFileHandlerTest, Monolog\Handler\SamplingHandlerTest, Monolog\Handler\SlackHandlerTest, Monolog\Handler\SlackWebhookHandlerTest, Monolog\Handler\Slack\SlackRecordTest, Monolog\Handler\SlackbotHandlerTest, Monolog\Handler\SocketHandlerTest, Monolog\Handler\StreamHandlerTest, Monolog\Handler\SwiftMailerHandlerTest, Monolog\Handler\SyslogHandlerTest, Monolog\Handler\SyslogUdpHandlerTest, Monolog\Handler\TestHandlerTest, Monolog\Handler\UdpSocketTest, Monolog\Handler\WhatFailureGroupHandlerTest, Monolog\Handler\ZendMonitorHandlerTest, Monolog\LoggerTest, Monolog\Processor\GitProcessorTest, Monolog\Processor\IntrospectionProcessorTest, Monolog\Processor\MemoryPeakUsageProcessorTest, Monolog\Processor\MemoryUsageProcessorTest, Monolog\Processor\MercurialProcessorTest, Monolog\Processor\ProcessIdProcessorTest, Monolog\Processor\PsrLogMessageProcessorTest, Monolog\Processor\TagProcessorTest, Monolog\Processor\UidProcessorTest, Monolog\Processor\WebProcessorTest, Monolog\PsrLogCompatTest, Monolog\RegistryTest, Monolog\TestCase, MultiDependencyTest, MultipleDataProviderTest, My\Space\ExceptionNamespaceTest, NamespaceCoverageClassExtendedTest, NamespaceCoverageClassTest, NamespaceCoverageCoversClassPublicTest, NamespaceCoverageCoversClassTest, NamespaceCoverageMethodTest, NamespaceCoverageNotPrivateTest, NamespaceCoverageNotProtectedTest, NamespaceCoverageNotPublicTest, NamespaceCoveragePrivateTest, NamespaceCoverageProtectedTest, NamespaceCoveragePublicTest, NoArgTestCaseTest, NoTestCases, NotExistingCoveredElementTest, NotPublicTestCase, NotVoidTestCase, NothingTest, OneTest, OneTestCase, OutputTestCase, OverrideTestCase, PHPUnit\Framework\TestCase, PHPUnit_Framework_Constraint_ArraySubsetTest, PHPUnit_Framework_IncompleteTestCase, PHPUnit_Framework_SkippedTestCase, PHPUnit_Framework_WarningTestCase, PHPUnit_Util_PHPTest, PhpParser\AutoloaderTest, PhpParser\BuilderFactoryTest, PhpParser\Builder\ClassTest, PhpParser\Builder\FunctionTest, PhpParser\Builder\InterfaceTest, PhpParser\Builder\MethodTest, PhpParser\Builder\NamespaceTest, PhpParser\Builder\ParamTest, PhpParser\Builder\PropertyTest, PhpParser\Builder\TraitTest, PhpParser\CodeParsingTest, PhpParser\CodeTestAbstract, PhpParser\CommentTest, PhpParser\ErrorHandler\CollectingTest, PhpParser\ErrorHandler\ThrowingTest, PhpParser\ErrorTest, PhpParser\LexerTest, PhpParser\Lexer\EmulativeTest, PhpParser\NodeAbstractTest, PhpParser\NodeDumperTest, PhpParser\NodeTraverserTest, PhpParser\NodeVisitor\NameResolverTest, PhpParser\Node\NameTest, PhpParser\Node\Scalar\MagicConstTest, PhpParser\Node\Scalar\StringTest, PhpParser\Node\Stmt\ClassConstTest, PhpParser\Node\Stmt\ClassMethodTest, PhpParser\Node\Stmt\ClassTest, PhpParser\Node\Stmt\InterfaceTest, PhpParser\Node\Stmt\PropertyTest, PhpParser\ParserFactoryTest, PhpParser\ParserTest, PhpParser\Parser\MultipleTest, PhpParser\Parser\Php5Test, PhpParser\Parser\Php7Test, PhpParser\PrettyPrinterTest, PhpParser\Serializer\XMLTest, PhpParser\Unserializer\XMLTest, RequirementsClassBeforeClassHookTest, RequirementsTest, Runner_BaseTestRunnerTest, SilverStripe\BehatExtens...SilverStripeContextTest, SilverStripe\Config\Test...hedConfigCollectionTest, SilverStripe\Config\Test...oryConfigCollectionTest, SilverStripe\Config\Tests\PriorityTest, SilverStripe\Config\Test...teStaticTransformerTest, SilverStripe\Config\Test...mer\YamlTransformerTest, SilverStripe\Control\Tests\ControllerTest, SilverStripe\Control\Tests\CookieJarTest, SilverStripe\Control\Tests\CookieTest, SilverStripe\Control\Tests\DirectorTest, SilverStripe\Control\Tests\Email\EmailTest, SilverStripe\Control\Tests\Email\SwiftMailerTest, SilverStripe\Control\Tests\Email\SwiftPluginTest, SilverStripe\Control\Tests\FlushRequestFilterTest, SilverStripe\Control\Tests\HTTPRequestTest, SilverStripe\Control\Tests\HTTPResponseTest, SilverStripe\Control\Tests\HTTPStreamResponseTest, SilverStripe\Control\Tests\HTTPTest, SilverStripe\Control\Tests\IPUtilsTest, SilverStripe\Control\Tests\NullHTTPRequestTest, SilverStripe\Control\Tes...xResponseNegotiatorTest, SilverStripe\Control\Tests\RSS\RSSFeedTest, SilverStripe\Control\Tests\RequestHandlingTest, SilverStripe\Control\Tests\SessionTest, SilverStripe\Core\Tests\Cache\CacheTest, SilverStripe\Core\Tests\ClassInfoTest, SilverStripe\Core\Tests\Config\ConfigTest, SilverStripe\Core\Tests\ConvertTest, SilverStripe\Core\Tests\CoreTest, SilverStripe\Core\Tests\HTMLCleanerTest, SilverStripe\Core\Tests\...tor\AopProxyServiceTest, SilverStripe\Core\Tests\Injector\InjectorTest, SilverStripe\Core\Tests\...ClassContentRemoverTest, SilverStripe\Core\Tests\Manifest\ClassLoaderTest, SilverStripe\Core\Tests\Manifest\ClassManifestTest, SilverStripe\Core\Tests\...fest\ConfigManifestTest, SilverStripe\Core\Tests\...\ManifestFileFinderTest, SilverStripe\Core\Tests\...fest\ModuleManifestTest, SilverStripe\Core\Tests\...spacedClassManifestTest, SilverStripe\Core\Tests\...ThemeResourceLoaderTest, SilverStripe\Core\Tests\MemoryLimitTest, SilverStripe\Core\Tests\ObjectTest, SilverStripe\Core\Tests\PhpSyntaxTest, SilverStripe\Core\Tests\...p\ErrorControlChainTest, SilverStripe\Core\Tests\...erConfirmationTokenTest, SilverStripe\Dev\FunctionalTest, SilverStripe\Dev\SapphireTest, SilverStripe\Dev\Tests\BacktraceTest, SilverStripe\Dev\Tests\BulkLoaderResultTest, SilverStripe\Dev\Tests\CSSContentParserTest, SilverStripe\Dev\Tests\CSVParserTest, SilverStripe\Dev\Tests\CsvBulkLoaderTest, SilverStripe\Dev\Tests\DeprecationTest, SilverStripe\Dev\Tests\DevAdminControllerTest, SilverStripe\Dev\Tests\FixtureBlueprintTest, SilverStripe\Dev\Tests\FixtureFactoryTest, SilverStripe\Dev\Tests\M...ConfigurationHelperTest, SilverStripe\Dev\Tests\SapphireTestTest, SilverStripe\Dev\Tests\TaskRunnerTest, SilverStripe\Dev\Tests\YamlFixtureTest, SilverStripe\Forms\Tests\CheckboxFieldTest, SilverStripe\Forms\Tests\CheckboxSetFieldTest, SilverStripe\Forms\Tests\CompositeFieldTest, SilverStripe\Forms\Tests...firmedPasswordFieldTest, SilverStripe\Forms\Tests\CurrencyFieldTest, SilverStripe\Forms\Tests\DateFieldTest, SilverStripe\Forms\Tests\DatetimeFieldTest, SilverStripe\Forms\Tests\DropdownFieldTest, SilverStripe\Forms\Tests\EmailFieldTest, SilverStripe\Forms\Tests...edShortcodeProviderTest, SilverStripe\Forms\Tests\EnumFieldTest, SilverStripe\Forms\Tests\FieldGroupTest, SilverStripe\Forms\Tests\FieldListTest, SilverStripe\Forms\Tests\FileFieldTest, SilverStripe\Forms\Tests\FormActionTest, SilverStripe\Forms\Tests\FormFactoryTest, SilverStripe\Forms\Tests\FormFieldTest, SilverStripe\Forms\Tests\FormRequestHandlerTest, SilverStripe\Forms\Tests\FormScaffolderTest, SilverStripe\Forms\Tests\FormSchemaTest, SilverStripe\Forms\Tests\FormTest, SilverStripe\Forms\Tests...istingAutocompleterTest, SilverStripe\Forms\Tests...idFieldAddNewButtonTest, SilverStripe\Forms\Tests...eld\GridFieldConfigTest, SilverStripe\Forms\Tests...ridFieldDataColumnsTest, SilverStripe\Forms\Tests...idFieldDeleteActionTest, SilverStripe\Forms\Tests...GridFieldDetailFormTest, SilverStripe\Forms\Tests...GridFieldEditButtonTest, SilverStripe\Forms\Tests...idFieldExportButtonTest, SilverStripe\Forms\Tests...\GridFieldPaginatorTest, SilverStripe\Forms\Tests...ridFieldPrintButtonTest, SilverStripe\Forms\Tests...FieldSortableHeaderTest, SilverStripe\Forms\Tests\GridField\GridFieldTest, SilverStripe\Forms\Tests...ridField_URLHandlerTest, SilverStripe\Forms\Tests\GroupedDropdownFieldTest, SilverStripe\Forms\Tests...or\HTMLEditorConfigTest, SilverStripe\Forms\Tests...tor\HTMLEditorFieldTest, SilverStripe\Forms\Tests...HTMLEditorSanitiserTest, SilverStripe\Forms\Tests\ListboxFieldTest, SilverStripe\Forms\Tests\LookupFieldTest, SilverStripe\Forms\Tests\MoneyFieldTest, SilverStripe\Forms\Tests\NullableFieldTests, SilverStripe\Forms\Tests\NumericFieldTest, SilverStripe\Forms\Tests\OptionsetFieldTest, SilverStripe\Forms\Tests\PopoverFieldTest, SilverStripe\Forms\Tests\RequiredFieldsTest, SilverStripe\Forms\Tests\SelectionGroupTest, SilverStripe\Forms\Tests\TextFieldTest, SilverStripe\Forms\Tests\TextareaFieldTest, SilverStripe\Forms\Tests\TimeFieldTest, SilverStripe\Forms\Tests\TreeDropdownFieldTest, SilverStripe\Forms\Tests\ValidatorTest, SilverStripe\Logging\Tes...endlyErrorFormatterTest, SilverStripe\Logging\Tes...ailedErrorFormatterTest, SilverStripe\Logging\Tests\HTTPOutputHandlerTest, SilverStripe\ORM\Tests\ArrayLibTest, SilverStripe\ORM\Tests\ArrayListTest, SilverStripe\ORM\Tests\ComponentSetTest, SilverStripe\ORM\Tests\DBClassNameTest, SilverStripe\ORM\Tests\DBCompositeTest, SilverStripe\ORM\Tests\DBCurrencyTest, SilverStripe\ORM\Tests\DBDateTest, SilverStripe\ORM\Tests\DBDatetimeTest, SilverStripe\ORM\Tests\DBFieldTest, SilverStripe\ORM\Tests\DBHTMLTextTest, SilverStripe\ORM\Tests\DBLocaleTest, SilverStripe\ORM\Tests\DBMoneyTest, SilverStripe\ORM\Tests\DBPercentageTest, SilverStripe\ORM\Tests\DBStringTest, SilverStripe\ORM\Tests\DBTest, SilverStripe\ORM\Tests\DBTextTest, SilverStripe\ORM\Tests\DBTimeTest, SilverStripe\ORM\Tests\DBYearTest, SilverStripe\ORM\Tests\DataExtensionTest, SilverStripe\ORM\Tests\DataListTest, SilverStripe\ORM\Tests\DataObjectDuplicationTest, SilverStripe\ORM\Tests\DataObjectLazyLoadingTest, SilverStripe\ORM\Tests\D...ectSchemaGenerationTest, SilverStripe\ORM\Tests\DataObjectSchemaTest, SilverStripe\ORM\Tests\DataObjectTest, SilverStripe\ORM\Tests\DataQueryTest, SilverStripe\ORM\Tests\DatabaseTest, SilverStripe\ORM\Tests\DecimalTest, SilverStripe\ORM\Tests\Filters\FulltextFilterTest, SilverStripe\ORM\Tests\F...FilterApplyRelationTest, SilverStripe\ORM\Tests\GroupedListTest, SilverStripe\ORM\Tests\HasManyListTest, SilverStripe\ORM\Tests\HierarchyTest, SilverStripe\ORM\Tests\LabelFieldTest, SilverStripe\ORM\Tests\ManyManyListExtensionTest, SilverStripe\ORM\Tests\ManyManyListTest, SilverStripe\ORM\Tests\ManyManyThroughListTest, SilverStripe\ORM\Tests\MapTest, SilverStripe\ORM\Tests\MarkedSetTest, SilverStripe\ORM\Tests\MySQLDatabaseTest, SilverStripe\ORM\Tests\PDODatabaseTest, SilverStripe\ORM\Tests\PaginatedListTest, SilverStripe\ORM\Tests\PolymorphicHasManyListTest, SilverStripe\ORM\Tests\SQLInsertTest, SilverStripe\ORM\Tests\SQLSelectTest, SilverStripe\ORM\Tests\SQLUpdateTest, SilverStripe\ORM\Tests\S...\FulltextSearchableTest, SilverStripe\ORM\Tests\Search\SearchContextTest, SilverStripe\ORM\Tests\TransactionTest, SilverStripe\ORM\Tests\URLSegmentFilterTest, SilverStripe\ORM\Tests\UnsavedRelationListTest, SilverStripe\ORM\Tests\ValidationExceptionTest, SilverStripe\ORM\Tests\ValidationResultTest, SilverStripe\Security\Tests\BasicAuthTest, SilverStripe\Security\Tests\GroupCsvBulkLoaderTest, SilverStripe\Security\Tests\GroupTest, SilverStripe\Security\Te...nheritedPermissionsTest, SilverStripe\Security\Te...MemberAuthenticatorTest, SilverStripe\Security\Te...MemberCsvBulkLoaderTest, SilverStripe\Security\Tests\MemberTest, SilverStripe\Security\Tests\PasswordEncryptorTest, SilverStripe\Security\Tests\PasswordValidatorTest, SilverStripe\Security\Te...ionCheckboxSetFieldTest, SilverStripe\Security\Tests\PermissionRoleTest, SilverStripe\Security\Tests\PermissionTest, SilverStripe\Security\Tests\RandomGeneratorTest, SilverStripe\Security\Te...ecurityDefaultAdminTest, SilverStripe\Security\Tests\SecurityTest, SilverStripe\Security\Tests\SecurityTokenTest, SilverStripe\View\Tests\ArrayDataTest, SilverStripe\View\Tests\ContentNegotiatorTest, SilverStripe\View\Tests\Parsers\DiffTest, SilverStripe\View\Tests\Parsers\HTML4ValueTest, SilverStripe\View\Tests\Parsers\SQLFormatterTest, SilverStripe\View\Tests\...ers\ShortcodeParserTest, SilverStripe\View\Tests\RequirementsTest, SilverStripe\View\Tests\SSViewerCacheBlockTest, SilverStripe\View\Tests\SSViewerTest, SilverStripe\View\Tests\ViewableDataTest, SilverStripe\i18n\Tests\YamlReaderTest, SilverStripe\i18n\Tests\YamlWriterTest, SilverStripe\i18n\Tests\i18nTest, SilverStripe\i18n\Tests\i18nTextCollectorTest, StackTest, StatusTest, StopsOnWarningTest, Success, TemplateMethodsTest, Test, TestAutoreferenced, TestDoxGroupTest, TestError, TestIncomplete, TestSkipped, TestWithTest, ThrowExceptionTestCase, ThrowNoExceptionTestCase, TwoTest, UseTest, Util_ConfigurationTest, Util_GetoptTest, Util_GlobalStateTest, Util_RegexTest, Util_TestDox_NamePrettifierTest, Util_TestTest, Util_XMLTest, WasRun. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
643
    }
644
645 View Code Duplication
    public static function assertNotContains(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
646
        $needle,
647
        $haystack,
648
        $message = '',
649
        $ignoreCase = false,
650
        $checkForObjectIdentity = true,
651
        $checkForNonObjectIdentity = false
652
    ) {
653
        if ($haystack instanceof DBField) {
654
            $haystack = (string)$haystack;
655
        }
656
        parent::assertNotContains($needle, $haystack, $message, $ignoreCase, $checkForObjectIdentity, $checkForNonObjectIdentity);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class PHPUnit_Framework_TestCase as the method assertNotContains() does only exist in the following sub-classes of PHPUnit_Framework_TestCase: AbstractTest, AssertionExampleTest, BankAccountTest, BankAccountWithCustomExtensionTest, BeforeAndAfterTest, BeforeClassAndAfterClassTest, BeforeClassWithOnlyDataProviderTest, ChangeCurrentWorkingDirectoryTest, ClonedDependencyTest, ConcreteTest, ConcreteWithMyCustomExtensionTest, CountTest, CoverageClassExtendedTest, CoverageClassTest, CoverageFunctionParenthesesTest, CoverageFunctionParenthesesWhitespaceTest, CoverageFunctionTest, CoverageMethodOneLineAnnotationTest, CoverageMethodParenthesesTest, CoverageMethodParenthesesWhitespaceTest, CoverageMethodTest, CoverageNamespacedFunctionTest, CoverageNoneTest, CoverageNotPrivateTest, CoverageNotProtectedTest, CoverageNotPublicTest, CoverageNothingTest, CoveragePrivateTest, CoverageProtectedTest, CoveragePublicTest, DataProviderDebugTest, DataProviderDependencyTest, DataProviderFilterTest, DataProviderIncompleteTest, DataProviderSkippedTest, DataProviderTest, DataProviderTestDoxTest, DependencyFailureTest, DependencySuccessTest, EmptyTestCaseTest, ExceptionInAssertPostConditionsTest, ExceptionInAssertPreConditionsTest, ExceptionInSetUpTest, ExceptionInTearDownTest, ExceptionInTest, ExceptionMessageRegExpTest, ExceptionMessageTest, ExceptionStackTest, ExceptionTest, Extensions_PhptTestCaseTest, Extensions_RepeatedTestTest, Failure, FailureTest, FatalTest, Foo_Bar_Issue684Test, Framework_AssertTest, Framework_BaseTestListenerTest, Framework_ConstraintTest, Framework_Constraint_IsJsonTest, Framework_Constraint_JsonMatchesTest, Framework_Constraint_Jso...rrorMessageProviderTest, Framework_SuiteTest, Framework_TestCaseTest, Framework_TestFailureTest, Framework_TestImplementorTest, Framework_TestListenerTest, IgnoreCodeCoverageClassTest, IncompleteTest, InheritanceA, InheritanceB, InheritedTestCase, IniTest, IsolationTest, Issue1021Test, Issue1149Test, Issue1216Test, Issue1265Test, Issue1330Test, Issue1335Test, Issue1337Test, Issue1348Test, Issue1351Test, Issue1374Test, Issue1437Test, Issue1468Test, Issue1471Test, Issue1472Test, Issue1570Test, Issue2137Test, Issue2145Test, Issue2158Test, Issue2366Test, Issue2380Test, Issue2382Test, Issue2435Test, Issue244Test, Issue322Test, Issue433Test, Issue445Test, Issue498Test, Issue503Test, Issue523Test, Issue578Test, Issue581Test, Issue74Test, Issue765Test, Issue797Test, MJS\TopSort\Tests\GroupedSortTest, MJS\TopSort\Tests\SimpleSortTest, MJS\TopSort\Tests\TestCase, Monolog\ErrorHandlerTest, Monolog\Formatter\ChromePHPFormatterTest, Monolog\Formatter\ElasticaFormatterTest, Monolog\Formatter\FlowdockFormatterTest, Monolog\Formatter\FluentdFormatterTest, Monolog\Formatter\GelfMessageFormatterTest, Monolog\Formatter\JsonFormatterTest, Monolog\Formatter\LineFormatterTest, Monolog\Formatter\LogglyFormatterTest, Monolog\Formatter\LogstashFormatterTest, Monolog\Formatter\MongoDBFormatterTest, Monolog\Formatter\NormalizerFormatterTest, Monolog\Formatter\ScalarFormatterTest, Monolog\Formatter\WildfireFormatterTest, Monolog\Handler\AbstractHandlerTest, Monolog\Handler\AbstractProcessingHandlerTest, Monolog\Handler\AmqpHandlerTest, Monolog\Handler\BrowserConsoleHandlerTest, Monolog\Handler\BufferHandlerTest, Monolog\Handler\ChromePHPHandlerTest, Monolog\Handler\CouchDBHandlerTest, Monolog\Handler\DeduplicationHandlerTest, Monolog\Handler\DoctrineCouchDBHandlerTest, Monolog\Handler\DynamoDbHandlerTest, Monolog\Handler\ElasticSearchHandlerTest, Monolog\Handler\ErrorLogHandlerTest, Monolog\Handler\FilterHandlerTest, Monolog\Handler\FingersCrossedHandlerTest, Monolog\Handler\FirePHPHandlerTest, Monolog\Handler\FleepHookHandlerTest, Monolog\Handler\FlowdockHandlerTest, Monolog\Handler\GelfHandlerLegacyTest, Monolog\Handler\GelfHandlerTest, Monolog\Handler\GroupHandlerTest, Monolog\Handler\HandlerWrapperTest, Monolog\Handler\HipChatHandlerTest, Monolog\Handler\LogEntriesHandlerTest, Monolog\Handler\MailHandlerTest, Monolog\Handler\MongoDBHandlerTest, Monolog\Handler\NativeMailerHandlerTest, Monolog\Handler\NewRelicHandlerTest, Monolog\Handler\NullHandlerTest, Monolog\Handler\PHPConsoleHandlerTest, Monolog\Handler\PsrHandlerTest, Monolog\Handler\PushoverHandlerTest, Monolog\Handler\RavenHandlerTest, Monolog\Handler\RedisHandlerTest, Monolog\Handler\RollbarHandlerTest, Monolog\Handler\RotatingFileHandlerTest, Monolog\Handler\SamplingHandlerTest, Monolog\Handler\SlackHandlerTest, Monolog\Handler\SlackWebhookHandlerTest, Monolog\Handler\Slack\SlackRecordTest, Monolog\Handler\SlackbotHandlerTest, Monolog\Handler\SocketHandlerTest, Monolog\Handler\StreamHandlerTest, Monolog\Handler\SwiftMailerHandlerTest, Monolog\Handler\SyslogHandlerTest, Monolog\Handler\SyslogUdpHandlerTest, Monolog\Handler\TestHandlerTest, Monolog\Handler\UdpSocketTest, Monolog\Handler\WhatFailureGroupHandlerTest, Monolog\Handler\ZendMonitorHandlerTest, Monolog\LoggerTest, Monolog\Processor\GitProcessorTest, Monolog\Processor\IntrospectionProcessorTest, Monolog\Processor\MemoryPeakUsageProcessorTest, Monolog\Processor\MemoryUsageProcessorTest, Monolog\Processor\MercurialProcessorTest, Monolog\Processor\ProcessIdProcessorTest, Monolog\Processor\PsrLogMessageProcessorTest, Monolog\Processor\TagProcessorTest, Monolog\Processor\UidProcessorTest, Monolog\Processor\WebProcessorTest, Monolog\PsrLogCompatTest, Monolog\RegistryTest, Monolog\TestCase, MultiDependencyTest, MultipleDataProviderTest, My\Space\ExceptionNamespaceTest, NamespaceCoverageClassExtendedTest, NamespaceCoverageClassTest, NamespaceCoverageCoversClassPublicTest, NamespaceCoverageCoversClassTest, NamespaceCoverageMethodTest, NamespaceCoverageNotPrivateTest, NamespaceCoverageNotProtectedTest, NamespaceCoverageNotPublicTest, NamespaceCoveragePrivateTest, NamespaceCoverageProtectedTest, NamespaceCoveragePublicTest, NoArgTestCaseTest, NoTestCases, NotExistingCoveredElementTest, NotPublicTestCase, NotVoidTestCase, NothingTest, OneTest, OneTestCase, OutputTestCase, OverrideTestCase, PHPUnit\Framework\TestCase, PHPUnit_Framework_Constraint_ArraySubsetTest, PHPUnit_Framework_IncompleteTestCase, PHPUnit_Framework_SkippedTestCase, PHPUnit_Framework_WarningTestCase, PHPUnit_Util_PHPTest, PhpParser\AutoloaderTest, PhpParser\BuilderFactoryTest, PhpParser\Builder\ClassTest, PhpParser\Builder\FunctionTest, PhpParser\Builder\InterfaceTest, PhpParser\Builder\MethodTest, PhpParser\Builder\NamespaceTest, PhpParser\Builder\ParamTest, PhpParser\Builder\PropertyTest, PhpParser\Builder\TraitTest, PhpParser\CodeParsingTest, PhpParser\CodeTestAbstract, PhpParser\CommentTest, PhpParser\ErrorHandler\CollectingTest, PhpParser\ErrorHandler\ThrowingTest, PhpParser\ErrorTest, PhpParser\LexerTest, PhpParser\Lexer\EmulativeTest, PhpParser\NodeAbstractTest, PhpParser\NodeDumperTest, PhpParser\NodeTraverserTest, PhpParser\NodeVisitor\NameResolverTest, PhpParser\Node\NameTest, PhpParser\Node\Scalar\MagicConstTest, PhpParser\Node\Scalar\StringTest, PhpParser\Node\Stmt\ClassConstTest, PhpParser\Node\Stmt\ClassMethodTest, PhpParser\Node\Stmt\ClassTest, PhpParser\Node\Stmt\InterfaceTest, PhpParser\Node\Stmt\PropertyTest, PhpParser\ParserFactoryTest, PhpParser\ParserTest, PhpParser\Parser\MultipleTest, PhpParser\Parser\Php5Test, PhpParser\Parser\Php7Test, PhpParser\PrettyPrinterTest, PhpParser\Serializer\XMLTest, PhpParser\Unserializer\XMLTest, RequirementsClassBeforeClassHookTest, RequirementsTest, Runner_BaseTestRunnerTest, SilverStripe\BehatExtens...SilverStripeContextTest, SilverStripe\Config\Test...hedConfigCollectionTest, SilverStripe\Config\Test...oryConfigCollectionTest, SilverStripe\Config\Tests\PriorityTest, SilverStripe\Config\Test...teStaticTransformerTest, SilverStripe\Config\Test...mer\YamlTransformerTest, SilverStripe\Control\Tests\ControllerTest, SilverStripe\Control\Tests\CookieJarTest, SilverStripe\Control\Tests\CookieTest, SilverStripe\Control\Tests\DirectorTest, SilverStripe\Control\Tests\Email\EmailTest, SilverStripe\Control\Tests\Email\SwiftMailerTest, SilverStripe\Control\Tests\Email\SwiftPluginTest, SilverStripe\Control\Tests\FlushRequestFilterTest, SilverStripe\Control\Tests\HTTPRequestTest, SilverStripe\Control\Tests\HTTPResponseTest, SilverStripe\Control\Tests\HTTPStreamResponseTest, SilverStripe\Control\Tests\HTTPTest, SilverStripe\Control\Tests\IPUtilsTest, SilverStripe\Control\Tests\NullHTTPRequestTest, SilverStripe\Control\Tes...xResponseNegotiatorTest, SilverStripe\Control\Tests\RSS\RSSFeedTest, SilverStripe\Control\Tests\RequestHandlingTest, SilverStripe\Control\Tests\SessionTest, SilverStripe\Core\Tests\Cache\CacheTest, SilverStripe\Core\Tests\ClassInfoTest, SilverStripe\Core\Tests\Config\ConfigTest, SilverStripe\Core\Tests\ConvertTest, SilverStripe\Core\Tests\CoreTest, SilverStripe\Core\Tests\HTMLCleanerTest, SilverStripe\Core\Tests\...tor\AopProxyServiceTest, SilverStripe\Core\Tests\Injector\InjectorTest, SilverStripe\Core\Tests\...ClassContentRemoverTest, SilverStripe\Core\Tests\Manifest\ClassLoaderTest, SilverStripe\Core\Tests\Manifest\ClassManifestTest, SilverStripe\Core\Tests\...fest\ConfigManifestTest, SilverStripe\Core\Tests\...\ManifestFileFinderTest, SilverStripe\Core\Tests\...fest\ModuleManifestTest, SilverStripe\Core\Tests\...spacedClassManifestTest, SilverStripe\Core\Tests\...ThemeResourceLoaderTest, SilverStripe\Core\Tests\MemoryLimitTest, SilverStripe\Core\Tests\ObjectTest, SilverStripe\Core\Tests\PhpSyntaxTest, SilverStripe\Core\Tests\...p\ErrorControlChainTest, SilverStripe\Core\Tests\...erConfirmationTokenTest, SilverStripe\Dev\FunctionalTest, SilverStripe\Dev\SapphireTest, SilverStripe\Dev\Tests\BacktraceTest, SilverStripe\Dev\Tests\BulkLoaderResultTest, SilverStripe\Dev\Tests\CSSContentParserTest, SilverStripe\Dev\Tests\CSVParserTest, SilverStripe\Dev\Tests\CsvBulkLoaderTest, SilverStripe\Dev\Tests\DeprecationTest, SilverStripe\Dev\Tests\DevAdminControllerTest, SilverStripe\Dev\Tests\FixtureBlueprintTest, SilverStripe\Dev\Tests\FixtureFactoryTest, SilverStripe\Dev\Tests\M...ConfigurationHelperTest, SilverStripe\Dev\Tests\SapphireTestTest, SilverStripe\Dev\Tests\TaskRunnerTest, SilverStripe\Dev\Tests\YamlFixtureTest, SilverStripe\Forms\Tests\CheckboxFieldTest, SilverStripe\Forms\Tests\CheckboxSetFieldTest, SilverStripe\Forms\Tests\CompositeFieldTest, SilverStripe\Forms\Tests...firmedPasswordFieldTest, SilverStripe\Forms\Tests\CurrencyFieldTest, SilverStripe\Forms\Tests\DateFieldTest, SilverStripe\Forms\Tests\DatetimeFieldTest, SilverStripe\Forms\Tests\DropdownFieldTest, SilverStripe\Forms\Tests\EmailFieldTest, SilverStripe\Forms\Tests...edShortcodeProviderTest, SilverStripe\Forms\Tests\EnumFieldTest, SilverStripe\Forms\Tests\FieldGroupTest, SilverStripe\Forms\Tests\FieldListTest, SilverStripe\Forms\Tests\FileFieldTest, SilverStripe\Forms\Tests\FormActionTest, SilverStripe\Forms\Tests\FormFactoryTest, SilverStripe\Forms\Tests\FormFieldTest, SilverStripe\Forms\Tests\FormRequestHandlerTest, SilverStripe\Forms\Tests\FormScaffolderTest, SilverStripe\Forms\Tests\FormSchemaTest, SilverStripe\Forms\Tests\FormTest, SilverStripe\Forms\Tests...istingAutocompleterTest, SilverStripe\Forms\Tests...idFieldAddNewButtonTest, SilverStripe\Forms\Tests...eld\GridFieldConfigTest, SilverStripe\Forms\Tests...ridFieldDataColumnsTest, SilverStripe\Forms\Tests...idFieldDeleteActionTest, SilverStripe\Forms\Tests...GridFieldDetailFormTest, SilverStripe\Forms\Tests...GridFieldEditButtonTest, SilverStripe\Forms\Tests...idFieldExportButtonTest, SilverStripe\Forms\Tests...\GridFieldPaginatorTest, SilverStripe\Forms\Tests...ridFieldPrintButtonTest, SilverStripe\Forms\Tests...FieldSortableHeaderTest, SilverStripe\Forms\Tests\GridField\GridFieldTest, SilverStripe\Forms\Tests...ridField_URLHandlerTest, SilverStripe\Forms\Tests\GroupedDropdownFieldTest, SilverStripe\Forms\Tests...or\HTMLEditorConfigTest, SilverStripe\Forms\Tests...tor\HTMLEditorFieldTest, SilverStripe\Forms\Tests...HTMLEditorSanitiserTest, SilverStripe\Forms\Tests\ListboxFieldTest, SilverStripe\Forms\Tests\LookupFieldTest, SilverStripe\Forms\Tests\MoneyFieldTest, SilverStripe\Forms\Tests\NullableFieldTests, SilverStripe\Forms\Tests\NumericFieldTest, SilverStripe\Forms\Tests\OptionsetFieldTest, SilverStripe\Forms\Tests\PopoverFieldTest, SilverStripe\Forms\Tests\RequiredFieldsTest, SilverStripe\Forms\Tests\SelectionGroupTest, SilverStripe\Forms\Tests\TextFieldTest, SilverStripe\Forms\Tests\TextareaFieldTest, SilverStripe\Forms\Tests\TimeFieldTest, SilverStripe\Forms\Tests\TreeDropdownFieldTest, SilverStripe\Forms\Tests\ValidatorTest, SilverStripe\Logging\Tes...endlyErrorFormatterTest, SilverStripe\Logging\Tes...ailedErrorFormatterTest, SilverStripe\Logging\Tests\HTTPOutputHandlerTest, SilverStripe\ORM\Tests\ArrayLibTest, SilverStripe\ORM\Tests\ArrayListTest, SilverStripe\ORM\Tests\ComponentSetTest, SilverStripe\ORM\Tests\DBClassNameTest, SilverStripe\ORM\Tests\DBCompositeTest, SilverStripe\ORM\Tests\DBCurrencyTest, SilverStripe\ORM\Tests\DBDateTest, SilverStripe\ORM\Tests\DBDatetimeTest, SilverStripe\ORM\Tests\DBFieldTest, SilverStripe\ORM\Tests\DBHTMLTextTest, SilverStripe\ORM\Tests\DBLocaleTest, SilverStripe\ORM\Tests\DBMoneyTest, SilverStripe\ORM\Tests\DBPercentageTest, SilverStripe\ORM\Tests\DBStringTest, SilverStripe\ORM\Tests\DBTest, SilverStripe\ORM\Tests\DBTextTest, SilverStripe\ORM\Tests\DBTimeTest, SilverStripe\ORM\Tests\DBYearTest, SilverStripe\ORM\Tests\DataExtensionTest, SilverStripe\ORM\Tests\DataListTest, SilverStripe\ORM\Tests\DataObjectDuplicationTest, SilverStripe\ORM\Tests\DataObjectLazyLoadingTest, SilverStripe\ORM\Tests\D...ectSchemaGenerationTest, SilverStripe\ORM\Tests\DataObjectSchemaTest, SilverStripe\ORM\Tests\DataObjectTest, SilverStripe\ORM\Tests\DataQueryTest, SilverStripe\ORM\Tests\DatabaseTest, SilverStripe\ORM\Tests\DecimalTest, SilverStripe\ORM\Tests\Filters\FulltextFilterTest, SilverStripe\ORM\Tests\F...FilterApplyRelationTest, SilverStripe\ORM\Tests\GroupedListTest, SilverStripe\ORM\Tests\HasManyListTest, SilverStripe\ORM\Tests\HierarchyTest, SilverStripe\ORM\Tests\LabelFieldTest, SilverStripe\ORM\Tests\ManyManyListExtensionTest, SilverStripe\ORM\Tests\ManyManyListTest, SilverStripe\ORM\Tests\ManyManyThroughListTest, SilverStripe\ORM\Tests\MapTest, SilverStripe\ORM\Tests\MarkedSetTest, SilverStripe\ORM\Tests\MySQLDatabaseTest, SilverStripe\ORM\Tests\PDODatabaseTest, SilverStripe\ORM\Tests\PaginatedListTest, SilverStripe\ORM\Tests\PolymorphicHasManyListTest, SilverStripe\ORM\Tests\SQLInsertTest, SilverStripe\ORM\Tests\SQLSelectTest, SilverStripe\ORM\Tests\SQLUpdateTest, SilverStripe\ORM\Tests\S...\FulltextSearchableTest, SilverStripe\ORM\Tests\Search\SearchContextTest, SilverStripe\ORM\Tests\TransactionTest, SilverStripe\ORM\Tests\URLSegmentFilterTest, SilverStripe\ORM\Tests\UnsavedRelationListTest, SilverStripe\ORM\Tests\ValidationExceptionTest, SilverStripe\ORM\Tests\ValidationResultTest, SilverStripe\Security\Tests\BasicAuthTest, SilverStripe\Security\Tests\GroupCsvBulkLoaderTest, SilverStripe\Security\Tests\GroupTest, SilverStripe\Security\Te...nheritedPermissionsTest, SilverStripe\Security\Te...MemberAuthenticatorTest, SilverStripe\Security\Te...MemberCsvBulkLoaderTest, SilverStripe\Security\Tests\MemberTest, SilverStripe\Security\Tests\PasswordEncryptorTest, SilverStripe\Security\Tests\PasswordValidatorTest, SilverStripe\Security\Te...ionCheckboxSetFieldTest, SilverStripe\Security\Tests\PermissionRoleTest, SilverStripe\Security\Tests\PermissionTest, SilverStripe\Security\Tests\RandomGeneratorTest, SilverStripe\Security\Te...ecurityDefaultAdminTest, SilverStripe\Security\Tests\SecurityTest, SilverStripe\Security\Tests\SecurityTokenTest, SilverStripe\View\Tests\ArrayDataTest, SilverStripe\View\Tests\ContentNegotiatorTest, SilverStripe\View\Tests\Parsers\DiffTest, SilverStripe\View\Tests\Parsers\HTML4ValueTest, SilverStripe\View\Tests\Parsers\SQLFormatterTest, SilverStripe\View\Tests\...ers\ShortcodeParserTest, SilverStripe\View\Tests\RequirementsTest, SilverStripe\View\Tests\SSViewerCacheBlockTest, SilverStripe\View\Tests\SSViewerTest, SilverStripe\View\Tests\ViewableDataTest, SilverStripe\i18n\Tests\YamlReaderTest, SilverStripe\i18n\Tests\YamlWriterTest, SilverStripe\i18n\Tests\i18nTest, SilverStripe\i18n\Tests\i18nTextCollectorTest, StackTest, StatusTest, StopsOnWarningTest, Success, TemplateMethodsTest, Test, TestAutoreferenced, TestDoxGroupTest, TestError, TestIncomplete, TestSkipped, TestWithTest, ThrowExceptionTestCase, ThrowNoExceptionTestCase, TwoTest, UseTest, Util_ConfigurationTest, Util_GetoptTest, Util_GlobalStateTest, Util_RegexTest, Util_TestDox_NamePrettifierTest, Util_TestTest, Util_XMLTest, WasRun. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
657
    }
658
659
    /**
660
     * Clear the log of emails sent
661
     */
662
    public function clearEmails()
663
    {
664
        return $this->mailer->clearEmails();
665
    }
666
667
    /**
668
     * Search for an email that was sent.
669
     * All of the parameters can either be a string, or, if they start with "/", a PREG-compatible regular expression.
670
     * @param $to
671
     * @param $from
672
     * @param $subject
673
     * @param $content
674
     * @return array Contains keys: 'type', 'to', 'from', 'subject','content', 'plainContent', 'attachedFiles',
675
     *               'customHeaders', 'htmlContent', 'inlineImages'
676
     */
677
    public function findEmail($to, $from = null, $subject = null, $content = null)
678
    {
679
        return $this->mailer->findEmail($to, $from, $subject, $content);
680
    }
681
682
    /**
683
     * Assert that the matching email was sent since the last call to clearEmails()
684
     * All of the parameters can either be a string, or, if they start with "/", a PREG-compatible regular expression.
685
     * @param $to
686
     * @param $from
687
     * @param $subject
688
     * @param $content
689
     * @return array Contains the keys: 'type', 'to', 'from', 'subject', 'content', 'plainContent', 'attachedFiles',
690
     *               'customHeaders', 'htmlContent', inlineImages'
691
     */
692
    public function assertEmailSent($to, $from = null, $subject = null, $content = null)
693
    {
694
        $found = (bool)$this->findEmail($to, $from, $subject, $content);
695
696
        $infoParts = "";
697
        $withParts = array();
698
        if ($to) {
699
            $infoParts .= " to '$to'";
700
        }
701
        if ($from) {
702
            $infoParts .= " from '$from'";
703
        }
704
        if ($subject) {
705
            $withParts[] = "subject '$subject'";
706
        }
707
        if ($content) {
708
            $withParts[] = "content '$content'";
709
        }
710
        if ($withParts) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $withParts 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...
711
            $infoParts .= " with " . implode(" and ", $withParts);
712
        }
713
714
        $this->assertTrue(
715
            $found,
716
            "Failed asserting that an email was sent$infoParts."
717
        );
718
    }
719
720
721
    /**
722
     * Assert that the given {@link SS_List} includes DataObjects matching the given key-value
723
     * pairs.  Each match must correspond to 1 distinct record.
724
     *
725
     * @param SS_List|array $matches The patterns to match.  Each pattern is a map of key-value pairs.  You can
726
     * either pass a single pattern or an array of patterns.
727
     * @param SS_List $dataObjectSet The {@link SS_List} to test.
728
     *
729
     * Examples
730
     * --------
731
     * Check that $members includes an entry with Email = [email protected]:
732
     *      $this->assertDOSContains(array('Email' => '[email protected]'), $members);
733
     *
734
     * Check that $members includes entries with Email = [email protected] and with
735
     * Email = [email protected]:
736
     *      $this->assertDOSContains(array(
737
     *         array('Email' => '[email protected]'),
738
     *         array('Email' => '[email protected]'),
739
     *      ), $members);
740
     */
741
    public function assertDOSContains($matches, $dataObjectSet)
742
    {
743
        $extracted = array();
744
        foreach ($dataObjectSet as $object) {
745
            /** @var DataObject $object */
746
            $extracted[] = $object->toMap();
747
        }
748
749 View Code Duplication
        foreach ($matches as $match) {
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...
750
            $matched = false;
751
            foreach ($extracted as $i => $item) {
752
                if ($this->dataObjectArrayMatch($item, $match)) {
753
                    // Remove it from $extracted so that we don't get duplicate mapping.
754
                    unset($extracted[$i]);
755
                    $matched = true;
756
                    break;
757
                }
758
            }
759
760
            // We couldn't find a match - assertion failed
761
            $this->assertTrue(
762
                $matched,
763
                "Failed asserting that the SS_List contains an item matching "
764
                . var_export($match, true) . "\n\nIn the following SS_List:\n"
765
                . $this->DOSSummaryForMatch($dataObjectSet, $match)
766
            );
767
        }
768
    }
769
    /**
770
     * Asserts that no items in a given list appear in the given dataobject list
771
     *
772
     * @param SS_List|array $matches The patterns to match.  Each pattern is a map of key-value pairs.  You can
773
     * either pass a single pattern or an array of patterns.
774
     * @param SS_List $dataObjectSet The {@link SS_List} to test.
775
     *
776
     * Examples
777
     * --------
778
     * Check that $members doesn't have an entry with Email = [email protected]:
779
     *      $this->assertNotDOSContains(array('Email' => '[email protected]'), $members);
780
     *
781
     * Check that $members doesn't have entries with Email = [email protected] and with
782
     * Email = [email protected]:
783
     *      $this->assertNotDOSContains(array(
784
     *         array('Email' => '[email protected]'),
785
     *         array('Email' => '[email protected]'),
786
     *      ), $members);
787
     */
788
    public function assertNotDOSContains($matches, $dataObjectSet)
789
    {
790
        $extracted = array();
791
        foreach ($dataObjectSet as $object) {
792
            /** @var DataObject $object */
793
            $extracted[] = $object->toMap();
794
        }
795
796
        $matched = [];
797
        foreach ($matches as $match) {
798
            foreach ($extracted as $i => $item) {
799
                if ($this->dataObjectArrayMatch($item, $match)) {
800
                    $matched[] = $extracted[$i];
801
                    break;
802
                }
803
            }
804
805
            // We couldn't find a match - assertion failed
806
            $this->assertEmpty(
807
                $matched,
808
                "Failed asserting that the SS_List dosn't contain a set of objects. "
809
                . "Found objects were: " . var_export($matched, true)
810
            );
811
        }
812
    }
813
814
    /**
815
     * Assert that the given {@link SS_List} includes only DataObjects matching the given
816
     * key-value pairs.  Each match must correspond to 1 distinct record.
817
     *
818
     * Example
819
     * --------
820
     * Check that *only* the entries Sam Minnee and Ingo Schommer exist in $members.  Order doesn't
821
     * matter:
822
     *     $this->assertDOSEquals(array(
823
     *        array('FirstName' =>'Sam', 'Surname' => 'Minnee'),
824
     *        array('FirstName' => 'Ingo', 'Surname' => 'Schommer'),
825
     *      ), $members);
826
     *
827
     * @param mixed $matches The patterns to match.  Each pattern is a map of key-value pairs.  You can
828
     * either pass a single pattern or an array of patterns.
829
     * @param mixed $dataObjectSet The {@link SS_List} to test.
830
     */
831
    public function assertDOSEquals($matches, $dataObjectSet)
832
    {
833
        // Extract dataobjects
834
        $extracted = array();
835
        if ($dataObjectSet) {
836
            foreach ($dataObjectSet as $object) {
837
                /** @var DataObject $object */
838
                $extracted[] = $object->toMap();
839
            }
840
        }
841
842
        // Check all matches
843
        if ($matches) {
844 View Code Duplication
            foreach ($matches as $match) {
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...
845
                $matched = false;
846
                foreach ($extracted as $i => $item) {
847
                    if ($this->dataObjectArrayMatch($item, $match)) {
848
                        // Remove it from $extracted so that we don't get duplicate mapping.
849
                        unset($extracted[$i]);
850
                        $matched = true;
851
                        break;
852
                    }
853
                }
854
855
                // We couldn't find a match - assertion failed
856
                $this->assertTrue(
857
                    $matched,
858
                    "Failed asserting that the SS_List contains an item matching "
859
                    . var_export($match, true) . "\n\nIn the following SS_List:\n"
860
                    . $this->DOSSummaryForMatch($dataObjectSet, $match)
861
                );
862
            }
863
        }
864
865
        // If we have leftovers than the DOS has extra data that shouldn't be there
866
        $this->assertTrue(
867
            (count($extracted) == 0),
868
            // If we didn't break by this point then we couldn't find a match
869
            "Failed asserting that the SS_List contained only the given items, the "
870
            . "following items were left over:\n" . var_export($extracted, true)
871
        );
872
    }
873
874
    /**
875
     * Assert that the every record in the given {@link SS_List} matches the given key-value
876
     * pairs.
877
     *
878
     * Example
879
     * --------
880
     * Check that every entry in $members has a Status of 'Active':
881
     *     $this->assertDOSAllMatch(array('Status' => 'Active'), $members);
882
     *
883
     * @param mixed $match The pattern to match.  The pattern is a map of key-value pairs.
884
     * @param mixed $dataObjectSet The {@link SS_List} to test.
885
     */
886
    public function assertDOSAllMatch($match, $dataObjectSet)
887
    {
888
        $extracted = array();
889
        foreach ($dataObjectSet as $object) {
890
            /** @var DataObject $object */
891
            $extracted[] = $object->toMap();
892
        }
893
894
        foreach ($extracted as $i => $item) {
895
            $this->assertTrue(
896
                $this->dataObjectArrayMatch($item, $match),
897
                "Failed asserting that the the following item matched "
898
                . var_export($match, true) . ": " . var_export($item, true)
899
            );
900
        }
901
    }
902
903
    /**
904
     * Removes sequences of repeated whitespace characters from SQL queries
905
     * making them suitable for string comparison
906
     *
907
     * @param string $sql
908
     * @return string The cleaned and normalised SQL string
909
     */
910
    protected function normaliseSQL($sql)
911
    {
912
        return trim(preg_replace('/\s+/m', ' ', $sql));
913
    }
914
915
    /**
916
     * Asserts that two SQL queries are equivalent
917
     *
918
     * @param string $expectedSQL
919
     * @param string $actualSQL
920
     * @param string $message
921
     * @param float|int $delta
922
     * @param integer $maxDepth
923
     * @param boolean $canonicalize
924
     * @param boolean $ignoreCase
925
     */
926
    public function assertSQLEquals(
927
        $expectedSQL,
928
        $actualSQL,
929
        $message = '',
930
        $delta = 0,
931
        $maxDepth = 10,
932
        $canonicalize = false,
933
        $ignoreCase = false
934
    ) {
935
        // Normalise SQL queries to remove patterns of repeating whitespace
936
        $expectedSQL = $this->normaliseSQL($expectedSQL);
937
        $actualSQL = $this->normaliseSQL($actualSQL);
938
939
        $this->assertEquals($expectedSQL, $actualSQL, $message, $delta, $maxDepth, $canonicalize, $ignoreCase);
940
    }
941
942
    /**
943
     * Asserts that a SQL query contains a SQL fragment
944
     *
945
     * @param string $needleSQL
946
     * @param string $haystackSQL
947
     * @param string $message
948
     * @param boolean $ignoreCase
949
     * @param boolean $checkForObjectIdentity
950
     */
951 View Code Duplication
    public function assertSQLContains(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
952
        $needleSQL,
953
        $haystackSQL,
954
        $message = '',
955
        $ignoreCase = false,
956
        $checkForObjectIdentity = true
957
    ) {
958
        $needleSQL = $this->normaliseSQL($needleSQL);
959
        $haystackSQL = $this->normaliseSQL($haystackSQL);
960
961
        $this->assertContains($needleSQL, $haystackSQL, $message, $ignoreCase, $checkForObjectIdentity);
962
    }
963
964
    /**
965
     * Asserts that a SQL query contains a SQL fragment
966
     *
967
     * @param string $needleSQL
968
     * @param string $haystackSQL
969
     * @param string $message
970
     * @param boolean $ignoreCase
971
     * @param boolean $checkForObjectIdentity
972
     */
973 View Code Duplication
    public function assertSQLNotContains(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
974
        $needleSQL,
975
        $haystackSQL,
976
        $message = '',
977
        $ignoreCase = false,
978
        $checkForObjectIdentity = true
979
    ) {
980
        $needleSQL = $this->normaliseSQL($needleSQL);
981
        $haystackSQL = $this->normaliseSQL($haystackSQL);
982
983
        $this->assertNotContains($needleSQL, $haystackSQL, $message, $ignoreCase, $checkForObjectIdentity);
984
    }
985
986
    /**
987
     * Helper function for the DOS matchers
988
     *
989
     * @param array $item
990
     * @param array $match
991
     * @return bool
992
     */
993
    private function dataObjectArrayMatch($item, $match)
994
    {
995
        foreach ($match as $k => $v) {
996
            if (!array_key_exists($k, $item) || $item[$k] != $v) {
997
                return false;
998
            }
999
        }
1000
        return true;
1001
    }
1002
1003
    /**
1004
     * Helper function for the DOS matchers
1005
     *
1006
     * @param SS_List|array $dataObjectSet
1007
     * @param array $match
1008
     * @return string
1009
     */
1010
    private function DOSSummaryForMatch($dataObjectSet, $match)
1011
    {
1012
        $extracted = array();
1013
        foreach ($dataObjectSet as $item) {
1014
            $extracted[] = array_intersect_key($item->toMap(), $match);
1015
        }
1016
        return var_export($extracted, true);
1017
    }
1018
1019
    /**
1020
     * Start test environment
1021
     */
1022
    public static function start()
1023
    {
1024
        if (!static::is_running_test()) {
1025
            new FakeController();
1026
            static::use_test_manifest();
1027
            static::set_is_running_test(true);
1028
        }
1029
    }
1030
1031
    /**
1032
     * Pushes a class and template manifest instance that include tests onto the
1033
     * top of the loader stacks.
1034
     */
1035
    protected static function use_test_manifest()
1036
    {
1037
        $flush = !empty($_GET['flush']);
1038
        $classManifest = new ClassManifest(
1039
            BASE_PATH,
1040
            true,
1041
            $flush
1042
        );
1043
1044
        ClassLoader::inst()->pushManifest($classManifest, false);
1045
        static::set_test_class_manifest($classManifest);
1046
1047
        ThemeResourceLoader::inst()->addSet('$default', new ThemeManifest(
1048
            BASE_PATH,
1049
            project(),
1050
            true,
1051
            $flush
1052
        ));
1053
1054
        // Once new class loader is registered, push a new uncached config
1055
        $config = CoreConfigFactory::inst()->createCore();
1056
        ConfigLoader::inst()->pushManifest($config);
1057
1058
        // Invalidate classname spec since the test manifest will now pull out new subclasses for each internal class
1059
        // (e.g. Member will now have various subclasses of DataObjects that implement TestOnly)
1060
        DataObject::reset();
1061
    }
1062
1063
    /**
1064
     * Returns true if we are currently using a temporary database
1065
     */
1066
    public static function using_temp_db()
1067
    {
1068
        $dbConn = DB::get_conn();
1069
        $prefix = getenv('SS_DATABASE_PREFIX') ?: 'ss_';
1070
        return 1 === preg_match(sprintf('/^%stmpdb_[0-9]+_[0-9]+$/i', preg_quote($prefix, '/')), $dbConn->getSelectedDatabase());
1071
    }
1072
1073
    public static function kill_temp_db()
1074
    {
1075
        // Delete our temporary database
1076
        if (self::using_temp_db()) {
1077
            $dbConn = DB::get_conn();
1078
            $dbName = $dbConn->getSelectedDatabase();
1079
            if ($dbName && DB::get_conn()->databaseExists($dbName)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $dbName of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1080
                // Some DataExtensions keep a static cache of information that needs to
1081
                // be reset whenever the database is killed
1082
                foreach (ClassInfo::subclassesFor(DataExtension::class) as $class) {
1083
                    $toCall = array($class, 'on_db_reset');
1084
                    if (is_callable($toCall)) {
1085
                        call_user_func($toCall);
1086
                    }
1087
                }
1088
1089
                // echo "Deleted temp database " . $dbConn->currentDatabase() . "\n";
1090
                $dbConn->dropSelectedDatabase();
1091
            }
1092
        }
1093
    }
1094
1095
    /**
1096
     * Remove all content from the temporary database.
1097
     */
1098
    public static function empty_temp_db()
1099
    {
1100
        if (self::using_temp_db()) {
1101
            DB::get_conn()->clearAllData();
1102
1103
            // Some DataExtensions keep a static cache of information that needs to
1104
            // be reset whenever the database is cleaned out
1105
            $classes = array_merge(ClassInfo::subclassesFor(DataExtension::class), ClassInfo::subclassesFor(DataObject::class));
1106
            foreach ($classes as $class) {
1107
                $toCall = array($class, 'on_db_reset');
1108
                if (is_callable($toCall)) {
1109
                    call_user_func($toCall);
1110
                }
1111
            }
1112
        }
1113
    }
1114
1115
    public static function create_temp_db()
1116
    {
1117
        // Disable PHPUnit error handling
1118
        $oldErrorHandler = set_error_handler(null);
1119
1120
        // Create a temporary database, and force the connection to use UTC for time
1121
        global $databaseConfig;
1122
        $databaseConfig['timezone'] = '+0:00';
1123
        DB::connect($databaseConfig);
1124
        $dbConn = DB::get_conn();
1125
        $prefix = getenv('SS_DATABASE_PREFIX') ?: 'ss_';
1126
        do {
1127
            $dbname = strtolower(sprintf('%stmpdb_%s_%s', $prefix, time(), rand(1000000, 9999999)));
1128
        } while ($dbConn->databaseExists($dbname));
1129
1130
        $dbConn->selectDatabase($dbname, true);
1131
1132
        static::resetDBSchema();
1133
1134
        // Reinstate PHPUnit error handling
1135
        set_error_handler($oldErrorHandler);
1136
1137
        // Ensure test db is killed on exit
1138
        register_shutdown_function(function () {
1139
            static::kill_temp_db();
1140
        });
1141
1142
        return $dbname;
1143
    }
1144
1145
    public static function delete_all_temp_dbs()
1146
    {
1147
        $prefix = getenv('SS_DATABASE_PREFIX') ?: 'ss_';
1148
        foreach (DB::get_schema()->databaseList() as $dbName) {
1149
            if (1 === preg_match(sprintf('/^%stmpdb_[0-9]+_[0-9]+$/i', preg_quote($prefix, '/')), $dbName)) {
1150
                DB::get_schema()->dropDatabase($dbName);
1151
                if (Director::is_cli()) {
1152
                    echo "Dropped database \"$dbName\"" . PHP_EOL;
1153
                } else {
1154
                    echo "<li>Dropped database \"$dbName\"</li>" . PHP_EOL;
1155
                }
1156
                flush();
1157
            }
1158
        }
1159
    }
1160
1161
    /**
1162
     * Reset the testing database's schema.
1163
     * @param bool $includeExtraDataObjects If true, the extraDataObjects tables will also be included
1164
     */
1165
    public static function resetDBSchema($includeExtraDataObjects = false)
1166
    {
1167
        if (self::using_temp_db()) {
1168
            DataObject::reset();
1169
1170
            // clear singletons, they're caching old extension info which is used in DatabaseAdmin->doBuild()
1171
            Injector::inst()->unregisterAllObjects();
1172
1173
            $dataClasses = ClassInfo::subclassesFor(DataObject::class);
1174
            array_shift($dataClasses);
1175
1176
            DB::quiet();
1177
            $schema = DB::get_schema();
1178
            $extraDataObjects = $includeExtraDataObjects ? static::getExtraDataObjects() : null;
1179
            $schema->schemaUpdate(function () use ($dataClasses, $extraDataObjects) {
1180
                foreach ($dataClasses as $dataClass) {
1181
                    // Check if class exists before trying to instantiate - this sidesteps any manifest weirdness
1182
                    if (class_exists($dataClass)) {
1183
                        $SNG = singleton($dataClass);
1184
                        if (!($SNG instanceof TestOnly)) {
1185
                            $SNG->requireTable();
1186
                        }
1187
                    }
1188
                }
1189
1190
                // If we have additional dataobjects which need schema, do so here:
1191
                if ($extraDataObjects) {
1192
                    foreach ($extraDataObjects as $dataClass) {
1193
                        $SNG = singleton($dataClass);
1194
                        if (singleton($dataClass) instanceof DataObject) {
1195
                            $SNG->requireTable();
1196
                        }
1197
                    }
1198
                }
1199
            });
1200
1201
            ClassInfo::reset_db_cache();
1202
            DataObject::singleton()->flushCache();
1203
        }
1204
    }
1205
1206
    /**
1207
     * Create a member and group with the given permission code, and log in with it.
1208
     * Returns the member ID.
1209
     *
1210
     * @param string|array $permCode Either a permission, or list of permissions
1211
     * @return int Member ID
1212
     */
1213
    public function logInWithPermission($permCode = "ADMIN")
1214
    {
1215
        if (is_array($permCode)) {
1216
            $permArray = $permCode;
1217
            $permCode = implode('.', $permCode);
1218
        } else {
1219
            $permArray = array($permCode);
1220
        }
1221
1222
        // Check cached member
1223
        if (isset($this->cache_generatedMembers[$permCode])) {
1224
            $member = $this->cache_generatedMembers[$permCode];
1225
        } else {
1226
            // Generate group with these permissions
1227
            $group = Group::create();
1228
            $group->Title = "$permCode group";
1229
            $group->write();
1230
1231
            // Create each individual permission
1232
            foreach ($permArray as $permArrayItem) {
1233
                $permission = Permission::create();
1234
                $permission->Code = $permArrayItem;
1235
                $permission->write();
1236
                $group->Permissions()->add($permission);
1237
            }
1238
1239
            $member = Member::get()->filter([
1240
                'Email' => "[email protected]",
1241
            ])->first();
1242
            if (!$member) {
1243
                $member = Member::create();
1244
            }
1245
1246
            $member->FirstName = $permCode;
0 ignored issues
show
Documentation introduced by
The property FirstName does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1247
            $member->Surname = "User";
0 ignored issues
show
Documentation introduced by
The property Surname does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1248
            $member->Email = "[email protected]";
0 ignored issues
show
Documentation introduced by
The property Email does not exist on object<SilverStripe\ORM\DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
1249
            $member->write();
1250
            $group->Members()->add($member);
1251
1252
            $this->cache_generatedMembers[$permCode] = $member;
1253
        }
1254
        $this->logInAs($member);
1255
        return $member->ID;
1256
    }
1257
1258
    /**
1259
     * Log in as the given member
1260
     *
1261
     * @param Member|int|string $member The ID, fixture codename, or Member object of the member that you want to log in
1262
     */
1263
    public function logInAs($member)
1264
    {
1265
        if (is_numeric($member)) {
1266
            $member = DataObject::get_by_id(Member::class, $member);
1267
        } elseif (!is_object($member)) {
1268
            $member = $this->objFromFixture(Member::class, $member);
1269
        }
1270
        Injector::inst()->get(IdentityStore::class)->logIn($member);
1271
    }
1272
1273
    /**
1274
     * Log out the current user
1275
     */
1276
    public function logOut()
1277
    {
1278
        Injector::inst()->get(IdentityStore::class)->logOut();
1279
    }
1280
1281
    /**
1282
     * Cache for logInWithPermission()
1283
     */
1284
    protected $cache_generatedMembers = array();
1285
1286
1287
    /**
1288
     * Test against a theme.
1289
     *
1290
     * @param string $themeBaseDir themes directory
1291
     * @param string $theme Theme name
1292
     * @param callable $callback
1293
     * @throws Exception
1294
     */
1295
    protected function useTestTheme($themeBaseDir, $theme, $callback)
1296
    {
1297
        Config::nest();
1298
1299
        if (strpos($themeBaseDir, BASE_PATH) === 0) {
1300
            $themeBaseDir = substr($themeBaseDir, strlen(BASE_PATH));
1301
        }
1302
        SSViewer::config()->update('theme_enabled', true);
1303
        SSViewer::set_themes([$themeBaseDir.'/themes/'.$theme, '$default']);
1304
1305
        $e = null;
1306
1307
        try {
1308
            $callback();
1309
        } catch (Exception $e) {
1310
        /* NOP for now, just save $e */
1311
        }
1312
1313
        Config::unnest();
1314
1315
        if ($e) {
1316
            throw $e;
1317
        }
1318
    }
1319
1320
    /**
1321
     * Get fixture paths for this test
1322
     *
1323
     * @return array List of paths
1324
     */
1325
    protected function getFixturePaths()
1326
    {
1327
        $fixtureFile = static::get_fixture_file();
1328
        if (empty($fixtureFile)) {
1329
            return [];
1330
        }
1331
1332
        $fixtureFiles = (is_array($fixtureFile)) ? $fixtureFile : [$fixtureFile];
1333
1334
        return array_map(function ($fixtureFilePath) {
1335
            return $this->resolveFixturePath($fixtureFilePath);
1336
        }, $fixtureFiles);
1337
    }
1338
1339
    /**
1340
     * Return all extra objects to scaffold for this test
1341
     * @return array
1342
     */
1343
    protected static function getExtraDataObjects()
1344
    {
1345
        return static::$extra_dataobjects;
1346
    }
1347
1348
    /**
1349
     * Get additional controller classes to register routes for
1350
     *
1351
     * @return array
1352
     */
1353
    protected static function getExtraControllers()
1354
    {
1355
        return static::$extra_controllers;
1356
    }
1357
1358
    /**
1359
     * Map a fixture path to a physical file
1360
     *
1361
     * @param string $fixtureFilePath
1362
     * @return string
1363
     */
1364
    protected function resolveFixturePath($fixtureFilePath)
1365
    {
1366
        // Support fixture paths relative to the test class, rather than relative to webroot
1367
        // String checking is faster than file_exists() calls.
1368
        $isRelativeToFile
1369
            = (strpos('/', $fixtureFilePath) === false)
1370
            || preg_match('/^(\.){1,2}/', $fixtureFilePath);
1371
1372
        if ($isRelativeToFile) {
1373
            $resolvedPath = realpath($this->getCurrentAbsolutePath() . '/' . $fixtureFilePath);
1374
            if ($resolvedPath) {
1375
                return $resolvedPath;
1376
            }
1377
        }
1378
1379
        // Check if file exists relative to base dir
1380
        $resolvedPath = realpath(Director::baseFolder() . '/' . $fixtureFilePath);
1381
        if ($resolvedPath) {
1382
            return $resolvedPath;
1383
        }
1384
1385
        return $fixtureFilePath;
1386
    }
1387
1388
    protected function setUpRoutes()
1389
    {
1390
        // Get overridden routes
1391
        $rules = $this->getExtraRoutes();
1392
1393
        // Add all other routes
1394
        foreach (Director::config()->uninherited('rules') as $route => $rule) {
1395
            if (!isset($rules[$route])) {
1396
                $rules[$route] = $rule;
1397
            }
1398
        }
1399
1400
        // Add default catch-all rule
1401
        $rules['$Controller//$Action/$ID/$OtherID'] = '*';
1402
1403
        // Add controller-name auto-routing
1404
        Director::config()->set('rules', $rules);
1405
    }
1406
1407
    /**
1408
     * Get extra routes to merge into Director.rules
1409
     *
1410
     * @return array
1411
     */
1412
    protected function getExtraRoutes()
1413
    {
1414
        $rules = [];
1415
        foreach ($this->getExtraControllers() as $class) {
1416
            $controllerInst = Controller::singleton($class);
1417
            $link = Director::makeRelative($controllerInst->Link());
1418
            $route = rtrim($link, '/') . '//$Action/$ID/$OtherID';
1419
            $rules[$route] = $class;
1420
        }
1421
        return $rules;
1422
    }
1423
}
1424