Completed
Push — Leksat-patch-1 ( 31c1c0...66f278 )
by Jonathan
01:23
created

RawDrupalContext::setUserManager()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
namespace Drupal\DrupalExtension\Context;
4
5
use Behat\MinkExtension\Context\RawMinkContext;
6
use Behat\Mink\Exception\DriverException;
7
use Behat\Testwork\Hook\HookDispatcher;
8
use Behat\Behat\Context\Environment\InitializedContextEnvironment;
9
10
use Drupal\DrupalDriverManager;
11
use Drupal\DrupalExtension\DrupalParametersTrait;
12
use Drupal\DrupalExtension\Manager\DrupalAuthenticationManagerInterface;
13
use Drupal\DrupalExtension\Manager\DrupalUserManagerInterface;
14
15
use Drupal\DrupalExtension\Hook\Scope\AfterLanguageEnableScope;
16
use Drupal\DrupalExtension\Hook\Scope\AfterNodeCreateScope;
17
use Drupal\DrupalExtension\Hook\Scope\AfterTermCreateScope;
18
use Drupal\DrupalExtension\Hook\Scope\AfterUserCreateScope;
19
use Drupal\DrupalExtension\Hook\Scope\BaseEntityScope;
20
use Drupal\DrupalExtension\Hook\Scope\BeforeLanguageEnableScope;
21
use Drupal\DrupalExtension\Hook\Scope\BeforeNodeCreateScope;
22
use Drupal\DrupalExtension\Hook\Scope\BeforeUserCreateScope;
23
use Drupal\DrupalExtension\Hook\Scope\BeforeTermCreateScope;
24
25
26
/**
27
 * Provides the raw functionality for interacting with Drupal.
28
 */
29
class RawDrupalContext extends RawMinkContext implements DrupalAwareInterface {
30
31
  use DrupalParametersTrait;
32
33
  /**
34
   * Drupal driver manager.
35
   *
36
   * @var \Drupal\DrupalDriverManager
37
   */
38
  private $drupal;
39
40
  /**
41
   * Event dispatcher object.
42
   *
43
   * @var \Behat\Testwork\Hook\HookDispatcher
44
   */
45
  protected $dispatcher;
46
47
  /**
48
   * Drupal authentication manager.
49
   *
50
   * @var \Drupal\DrupalExtension\Manager\DrupalAuthenticationManagerInterface
51
   */
52
  protected $authenticationManager;
53
54
  /**
55
   * Drupal user manager.
56
   *
57
   * @var \Drupal\DrupalExtension\Manager\DrupalUserManagerInterface
58
   */
59
  protected $userManager;
60
61
  /**
62
   * Keep track of nodes so they can be cleaned up.
63
   *
64
   * @var array
65
   */
66
  protected $nodes = array();
67
68
  /**
69
   * Keep track of all terms that are created so they can easily be removed.
70
   *
71
   * @var array
72
   */
73
  protected $terms = array();
74
75
  /**
76
   * Keep track of any roles that are created so they can easily be removed.
77
   *
78
   * @var array
79
   */
80
  protected $roles = array();
81
82
  /**
83
   * Keep track of any languages that are created so they can easily be removed.
84
   *
85
   * @var array
86
   */
87
  protected $languages = array();
88
89
  /**
90
   * {@inheritDoc}
91
   */
92
  public function setDrupal(DrupalDriverManager $drupal) {
93
    $this->drupal = $drupal;
94
  }
95
96
  /**
97
   * {@inheritDoc}
98
   */
99
  public function getDrupal() {
100
    return $this->drupal;
101
  }
102
103
  /**
104
   * {@inheritDoc}
105
   */
106
  public function setUserManager(DrupalUserManagerInterface $userManager) {
107
    $this->userManager = $userManager;
108
  }
109
110
  /**
111
   * {@inheritdoc}
112
   */
113
  public function getUserManager() {
114
    return $this->userManager;
115
  }
116
117
  /**
118
   * {@inheritdoc}
119
   */
120
  public function setAuthenticationManager(DrupalAuthenticationManagerInterface $authenticationManager) {
121
    $this->authenticationManager = $authenticationManager;
122
  }
123
124
  /**
125
   * {@inheritdoc}
126
   */
127
  public function getAuthenticationManager() {
128
    return $this->authenticationManager;
129
  }
130
131
  /**
132
   * Magic setter.
133
   */
134
  public function __set($name, $value) {
135
    switch ($name) {
136
      case 'user':
137
        trigger_error('Interacting directly with the RawDrupalContext::$user property has been deprecated. Use RawDrupalContext::getUserManager->setCurrentUser() instead.', E_USER_DEPRECATED);
138
        // Set the user on the user manager service, so it is shared between all
139
        // contexts.
140
        $this->getUserManager()->setCurrentUser($value);
141
        break;
142
143
      case 'users':
144
        trigger_error('Interacting directly with the RawDrupalContext::$users property has been deprecated. Use RawDrupalContext::getUserManager->addUser() instead.', E_USER_DEPRECATED);
145
        // Set the user on the user manager service, so it is shared between all
146
        // contexts.
147
        if (empty($value)) {
148
          $this->getUserManager()->clearUsers();
149
        }
150
        else {
151
          foreach ($value as $user) {
152
            $this->getUserManager()->addUser($user);
153
          }
154
        }
155
        break;
156
    }
157
  }
158
159
  /**
160
   * Magic getter.
161
   */
162
  public function __get($name) {
163
    switch ($name) {
164
      case 'user':
165
        trigger_error('Interacting directly with the RawDrupalContext::$user property has been deprecated. Use RawDrupalContext::getUserManager->getCurrentUser() instead.', E_USER_DEPRECATED);
166
        // Returns the current user from the user manager service. This is shared
167
        // between all contexts.
168
        return $this->getUserManager()->getCurrentUser();
169
170
      case 'users':
171
        trigger_error('Interacting directly with the RawDrupalContext::$users property has been deprecated. Use RawDrupalContext::getUserManager->getUsers() instead.', E_USER_DEPRECATED);
172
        // Returns the current user from the user manager service. This is shared
173
        // between all contexts.
174
        return $this->getUserManager()->getUsers();
175
    }
176
  }
177
178
  /**
179
   * {@inheritdoc}
180
   */
181
  public function setDispatcher(HookDispatcher $dispatcher) {
182
    $this->dispatcher = $dispatcher;
183
  }
184
185
  /**
186
   * Get active Drupal Driver.
187
   *
188
   * @return \Drupal\Driver\DrupalDriver
189
   */
190
  public function getDriver($name = NULL) {
191
    return $this->getDrupal()->getDriver($name);
192
  }
193
194
  /**
195
   * Get driver's random generator.
196
   */
197
  public function getRandom() {
198
    return $this->getDriver()->getRandom();
199
  }
200
201
  /**
202
   * Massage node values to match the expectations on different Drupal versions.
203
   *
204
   * @beforeNodeCreate
205
   */
206
  public static function alterNodeParameters(BeforeNodeCreateScope $scope) {
207
    $node = $scope->getEntity();
208
209
    // Get the Drupal API version if available. This is not available when
210
    // using e.g. the BlackBoxDriver or DrushDriver.
211
    $api_version = NULL;
212
    $driver = $scope->getContext()->getDrupal()->getDriver();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Behat\Behat\Context\Context as the method getDrupal() does only exist in the following implementations of said interface: Drupal\DrupalExtension\Context\ConfigContext, Drupal\DrupalExtension\Context\DrupalContext, Drupal\DrupalExtension\C...xt\DrupalSubContextBase, Drupal\DrupalExtension\Context\DrushContext, Drupal\DrupalExtension\Context\MessageContext, Drupal\DrupalExtension\Context\RawDrupalContext, FeatureContext, FooFoo.

Let’s take a look at an example:

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

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
213
    if ($driver instanceof \Drupal\Driver\DrupalDriver) {
214
      $api_version = $scope->getContext()->getDrupal()->getDriver()->version;
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Behat\Behat\Context\Context as the method getDrupal() does only exist in the following implementations of said interface: Drupal\DrupalExtension\Context\ConfigContext, Drupal\DrupalExtension\Context\DrupalContext, Drupal\DrupalExtension\C...xt\DrupalSubContextBase, Drupal\DrupalExtension\Context\DrushContext, Drupal\DrupalExtension\Context\MessageContext, Drupal\DrupalExtension\Context\RawDrupalContext, FeatureContext, FooFoo.

Let’s take a look at an example:

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

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
215
    }
216
217
    // On Drupal 8 the timestamps should be in UNIX time.
218
    switch ($api_version) {
219
      case 8:
220
        foreach (array('changed', 'created', 'revision_timestamp') as $field) {
221
          if (!empty($node->$field) && !is_numeric($node->$field)) {
222
            $node->$field = strtotime($node->$field);
223
          }
224
        }
225
      break;
226
    }
227
  }
228
229
  /**
230
   * Remove any created nodes.
231
   *
232
   * @AfterScenario
233
   */
234
  public function cleanNodes() {
235
    // Remove any nodes that were created.
236
    foreach ($this->nodes as $node) {
237
      $this->getDriver()->nodeDelete($node);
238
    }
239
    $this->nodes = array();
240
  }
241
242
  /**
243
   * Remove any created users.
244
   *
245
   * @AfterScenario
246
   */
247
  public function cleanUsers() {
248
    // Remove any users that were created.
249
    if ($this->userManager->hasUsers()) {
250
      foreach ($this->userManager->getUsers() as $user) {
251
        $this->getDriver()->userDelete($user);
252
      }
253
      $this->getDriver()->processBatch();
254
      $this->userManager->clearUsers();
255
      if ($this->loggedIn()) {
256
        $this->logout();
257
      }
258
    }
259
  }
260
261
  /**
262
   * Remove any created terms.
263
   *
264
   * @AfterScenario
265
   */
266
  public function cleanTerms() {
267
    // Remove any terms that were created.
268
    foreach ($this->terms as $term) {
269
      $this->getDriver()->termDelete($term);
270
    }
271
    $this->terms = array();
272
  }
273
274
  /**
275
   * Remove any created roles.
276
   *
277
   * @AfterScenario
278
   */
279
  public function cleanRoles() {
280
    // Remove any roles that were created.
281
    foreach ($this->roles as $rid) {
282
      $this->getDriver()->roleDelete($rid);
283
    }
284
    $this->roles = array();
285
  }
286
287
  /**
288
   * Remove any created languages.
289
   *
290
   * @AfterScenario
291
   */
292
  public function cleanLanguages() {
293
    // Delete any languages that were created.
294
    foreach ($this->languages as $language) {
295
      $this->getDriver()->languageDelete($language);
296
      unset($this->languages[$language->langcode]);
297
    }
298
  }
299
300
  /**
301
   * Clear static caches.
302
   *
303
   * @AfterScenario @api
304
   */
305
  public function clearStaticCaches() {
306
    $this->getDriver()->clearStaticCaches();
307
  }
308
309
  /**
310
   * Dispatch scope hooks.
311
   *
312
   * @param string $scope
0 ignored issues
show
Bug introduced by
There is no parameter named $scope. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
313
   *   The entity scope to dispatch.
314
   * @param \stdClass $entity
315
   *   The entity.
316
   */
317
  protected function dispatchHooks($scopeType, \stdClass $entity) {
318
    $fullScopeClass = 'Drupal\\DrupalExtension\\Hook\\Scope\\' . $scopeType;
319
    $scope = new $fullScopeClass($this->getDrupal()->getEnvironment(), $this, $entity);
320
    $callResults = $this->dispatcher->dispatchScopeHooks($scope);
321
322
    // The dispatcher suppresses exceptions, throw them here if there are any.
323
    foreach ($callResults as $result) {
324
      if ($result->hasException()) {
325
        $exception = $result->getException();
326
        throw $exception;
327
      }
328
    }
329
  }
330
331
  /**
332
   * Create a node.
333
   *
334
   * @return object
335
   *   The created node.
336
   */
337 View Code Duplication
  public function nodeCreate($node) {
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...
338
    $this->dispatchHooks('BeforeNodeCreateScope', $node);
339
    $this->parseEntityFields('node', $node);
340
    $saved = $this->getDriver()->createNode($node);
341
    $this->dispatchHooks('AfterNodeCreateScope', $saved);
342
    $this->nodes[] = $saved;
343
    return $saved;
344
  }
345
346
  /**
347
   * Parses the field values and turns them into the format expected by Drupal.
348
   *
349
   * Multiple values in a single field must be separated by commas. Wrap the
350
   * field value in double quotes in case it should contain a comma.
351
   *
352
   * Compound field properties are identified using a ':' operator, either in
353
   * the column heading or in the cell. If multiple properties are present in a
354
   * single cell, they must be separated using ' - ', and values should not
355
   * contain ':' or ' - '.
356
   *
357
   * Possible formats for the values:
358
   *   A
359
   *   A, B, "a value, containing a comma"
360
   *   A - B
361
   *   x: A - y: B
362
   *   A - B, C - D, "E - F"
363
   *   x: A - y: B,  x: C - y: D,  "x: E - y: F"
364
   *
365
   * See field_handlers.feature for examples of usage.
366
   *
367
   * @param string $entity_type
368
   *   The entity type.
369
   * @param \stdClass $entity
370
   *   An object containing the entity properties and fields as properties.
371
   *
372
   * @throws \Exception
373
   *   Thrown when a field name is invalid.
374
   */
375
  public function parseEntityFields($entity_type, \stdClass $entity) {
376
    $multicolumn_field = '';
377
    $multicolumn_fields = array();
378
379
    foreach (clone $entity as $field => $field_value) {
0 ignored issues
show
Bug introduced by
The expression clone $entity of type object<stdClass> is not traversable.
Loading history...
380
      // Reset the multicolumn field if the field name does not contain a column.
381
      if (strpos($field, ':') === FALSE) {
382
        $multicolumn_field = '';
383
      }
384
      // Start tracking a new multicolumn field if the field name contains a ':'
385
      // which is preceded by at least 1 character.
386
      elseif (strpos($field, ':', 1) !== FALSE) {
387
        list($multicolumn_field, $multicolumn_column) = explode(':', $field);
388
      }
389
      // If a field name starts with a ':' but we are not yet tracking a
390
      // multicolumn field we don't know to which field this belongs.
391
      elseif (empty($multicolumn_field)) {
392
        throw new \Exception('Field name missing for ' . $field);
393
      }
394
      // Update the column name if the field name starts with a ':' and we are
395
      // already tracking a multicolumn field.
396
      else {
397
        $multicolumn_column = substr($field, 1);
398
      }
399
400
      $is_multicolumn = $multicolumn_field && $multicolumn_column;
0 ignored issues
show
Bug introduced by
The variable $multicolumn_column does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
401
      $field_name = $multicolumn_field ?: $field;
402
      if ($this->getDriver()->isField($entity_type, $field_name)) {
403
        // Split up multiple values in multi-value fields.
404
        $values = array();
405
        foreach (str_getcsv($field_value) as $key => $value) {
406
          $value = trim($value);
407
          $columns = $value;
408
          // Split up field columns if the ' - ' separator is present.
409
          if (strstr($value, ' - ') !== FALSE) {
410
            $columns = array();
411
            foreach (explode(' - ', $value) as $column) {
412
              // Check if it is an inline named column.
413
              if (!$is_multicolumn && strpos($column, ': ', 1) !== FALSE) {
414
                list ($key, $column) = explode(': ', $column);
415
                $columns[$key] = $column;
416
              }
417
              else {
418
                $columns[] = $column;
419
              }
420
            }
421
          }
422
          // Use the column name if we are tracking a multicolumn field.
423
          if ($is_multicolumn) {
424
            $multicolumn_fields[$multicolumn_field][$key][$multicolumn_column] = $columns;
425
            unset($entity->$field);
426
          }
427
          else {
428
            $values[] = $columns;
429
          }
430
        }
431
        // Replace regular fields inline in the entity after parsing.
432
        if (!$is_multicolumn) {
433
          $entity->$field_name = $values;
434
          // Don't specify any value if the step author has left it blank.
435
          if ($field_value === '') {
436
            unset($entity->$field_name);
437
          }
438
        }
439
      }
440
    }
441
442
    // Add the multicolumn fields to the entity.
443
    foreach ($multicolumn_fields as $field_name => $columns) {
444
      // Don't specify any value if the step author has left it blank.
445
      if (count(array_filter($columns, function ($var) {
446
        return ($var !== '');
447
      })) > 0) {
448
        $entity->$field_name = $columns;
449
      }
450
    }
451
  }
452
453
  /**
454
   * Create a user.
455
   *
456
   * @return object
457
   *   The created user.
458
   */
459
  public function userCreate($user) {
460
    $this->dispatchHooks('BeforeUserCreateScope', $user);
461
    $this->parseEntityFields('user', $user);
462
    $this->getDriver()->userCreate($user);
463
    $this->dispatchHooks('AfterUserCreateScope', $user);
464
    $this->userManager->addUser($user);
465
    return $user;
466
  }
467
468
  /**
469
   * Create a term.
470
   *
471
   * @return object
472
   *   The created term.
473
   */
474 View Code Duplication
  public function termCreate($term) {
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...
475
    $this->dispatchHooks('BeforeTermCreateScope', $term);
476
    $this->parseEntityFields('taxonomy_term', $term);
477
    $saved = $this->getDriver()->createTerm($term);
478
    $this->dispatchHooks('AfterTermCreateScope', $saved);
479
    $this->terms[] = $saved;
480
    return $saved;
481
  }
482
483
  /**
484
   * Creates a language.
485
   *
486
   * @param \stdClass $language
487
   *   An object with the following properties:
488
   *   - langcode: the langcode of the language to create.
489
   *
490
   * @return object|FALSE
491
   *   The created language, or FALSE if the language was already created.
492
   */
493
  public function languageCreate(\stdClass $language) {
494
    $this->dispatchHooks('BeforeLanguageCreateScope', $language);
495
    $language = $this->getDriver()->languageCreate($language);
496
    if ($language) {
497
      $this->dispatchHooks('AfterLanguageCreateScope', $language);
498
      $this->languages[$language->langcode] = $language;
499
    }
500
    return $language;
501
  }
502
503
  /**
504
   * Log-in the given user.
505
   *
506
   * @param \stdClass $user
507
   *   The user to log in.
508
   */
509
  public function login(\stdClass $user) {
510
    $this->getAuthenticationManager()->logIn($user);
511
  }
512
513
  /**
514
   * Logs the current user out.
515
   */
516
  public function logout() {
517
    $this->getAuthenticationManager()->logOut();
518
  }
519
520
  /**
521
   * Determine if the a user is already logged in.
522
   *
523
   * @return boolean
524
   *   Returns TRUE if a user is logged in for this session.
525
   */
526
  public function loggedIn() {
527
    return $this->getAuthenticationManager()->loggedIn();
528
  }
529
530
  /**
531
   * User with a given role is already logged in.
532
   *
533
   * @param string $role
534
   *   A single role, or multiple comma-separated roles in a single string.
535
   *
536
   * @return boolean
537
   *   Returns TRUE if the current logged in user has this role (or roles).
538
   */
539
  public function loggedInWithRole($role) {
540
    return $this->loggedIn() && $this->getUserManager()->currentUserHasRole($role);
541
  }
542
543
  /**
544
   * Returns the Behat context that corresponds with the given class name.
545
   *
546
   * This is inspired by InitializedContextEnvironment::getContext() but also
547
   * returns subclasses of the given class name. This allows us to retrieve for
548
   * example DrupalContext even if it is overridden in a project.
549
   *
550
   * @param string $class
551
   *   A fully namespaced class name.
552
   *
553
   * @return \Behat\Behat\Context\Context|false
554
   *   The requested context, or FALSE if the context is not registered.
555
   *
556
   * @throws \Exception
557
   *   Thrown when the environment is not yet initialized, meaning that contexts
558
   *   cannot yet be retrieved.
559
   */
560
  protected function getContext($class) {
561
    /** @var InitializedContextEnvironment $environment */
562
    $environment = $this->drupal->getEnvironment();
563
    // Throw an exception if the environment is not yet initialized. To make
564
    // sure state doesn't leak between test scenarios, the environment is
565
    // reinitialized at the start of every scenario. If this code is executed
566
    // before a test scenario starts (e.g. in a `@BeforeScenario` hook) then the
567
    // contexts cannot yet be retrieved.
568
    if (!$environment instanceof InitializedContextEnvironment) {
569
      throw new \Exception('Cannot retrieve contexts when the environment is not yet initialized.');
570
    }
571
    foreach ($environment->getContexts() as $context) {
572
      if ($context instanceof $class) {
573
        return $context;
574
      }
575
    }
576
577
    return FALSE;
578
  }
579
580
}
581