GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — 8.x-2.x ( 0e9239...19b429 )
by Samuel
03:38
created

PanelizerThumbnailFormatter::settingsForm()   B

Complexity

Conditions 5
Paths 2

Size

Total Lines 27
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 18
nc 2
nop 2
dl 0
loc 27
rs 8.439
c 0
b 0
f 0
1
<?php
2
3
namespace Drupal\df_tools_panelizer\Plugin\Field\FieldFormatter;
4
5
use Drupal\block_content\Plugin\Block\BlockContentBlock;
6
use Drupal\Core\Entity\EntityRepositoryInterface;
7
use Drupal\Core\Entity\EntityStorageInterface;
8
use Drupal\Core\Entity\FieldableEntityInterface;
9
use Drupal\Core\Field\EntityReferenceFieldItemList;
10
use Drupal\Core\Field\FieldDefinitionInterface;
11
use Drupal\Core\Field\FieldItemListInterface;
12
use Drupal\Core\Field\FormatterBase;
13
use Drupal\Core\Form\FormStateInterface;
14
use Drupal\Core\Image\ImageFactory;
15
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
16
use Drupal\ctools_block\Plugin\Block\EntityField;
17
use Drupal\file\FileInterface;
18
use Drupal\media_entity\MediaInterface;
19
use Drupal\panelizer\PanelizerInterface;
20
use Symfony\Component\DependencyInjection\ContainerInterface;
21
22
/**
23
 * Renders a thumbnail based on a given Panelizer field.
24
 *
25
 * @FieldFormatter(
26
 *   id = "panelizer_thumbnail",
27
 *   label = @Translation("Panelizer Thumbnail"),
28
 *   field_types = {
29
 *     "panelizer"
30
 *   }
31
 * )
32
 */
33
class PanelizerThumbnailFormatter extends FormatterBase implements ContainerFactoryPluginInterface {
34
35
  /**
36
   * How deep this formatter should look for a file.
37
   *
38
   * @var int
39
   */
40
  const DEPTH_LIMIT = 5;
41
42
  /**
43
   * @var \Drupal\Core\Entity\EntityStorageInterface
44
   */
45
  protected $responsiveImageStyleStorage;
46
47
  /**
48
   * @var \Drupal\panelizer\PanelizerInterface
49
   */
50
  protected $panelizer;
51
52
  /**
53
   * @var \Drupal\Core\Image\ImageFactory
54
   */
55
  protected $imageFactory;
56
57
  /**
58
   * @var \Drupal\Core\Entity\EntityRepositoryInterface
59
   */
60
  protected $entityRepository;
61
62
  /**
63
   * Constructs a PanelizerThumbnailFormatter object.
64
   *
65
   * @param string $plugin_id
66
   *   The plugin_id for the formatter.
67
   * @param mixed $plugin_definition
68
   *   The plugin implementation definition.
69
   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
70
   *   The definition of the field to which the formatter is associated.
71
   * @param array $settings
72
   *   The formatter settings.
73
   * @param string $label
74
   *   The formatter label display setting.
75
   * @param string $view_mode
76
   *   The view mode.
77
   * @param array $third_party_settings
78
   *   Any third party settings.
79
   * @param \Drupal\Core\Entity\EntityStorageInterface $responsive_image_style_storage
80
   *   The responsive image style storage.
81
   * @param \Drupal\panelizer\PanelizerInterface $panelizer
82
   *   The panelizer service.
83
   */
84
  public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, $label, $view_mode, array $third_party_settings, EntityStorageInterface $responsive_image_style_storage, PanelizerInterface $panelizer, ImageFactory $image_factory, EntityRepositoryInterface $entity_repository) {
85
    parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $label, $view_mode, $third_party_settings);
86
87
    $this->responsiveImageStyleStorage = $responsive_image_style_storage;
88
    $this->panelizer = $panelizer;
89
    $this->imageFactory = $image_factory;
90
    $this->entityRepository = $entity_repository;
91
  }
92
93
  /**
94
   * {@inheritdoc}
95
   */
96
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
97
    return new static(
98
      $plugin_id,
99
      $plugin_definition,
100
      $configuration['field_definition'],
101
      $configuration['settings'],
102
      $configuration['label'],
103
      $configuration['view_mode'],
104
      $configuration['third_party_settings'],
105
      $container->get('entity.manager')->getStorage('responsive_image_style'),
106
      $container->get('panelizer'),
107
      $container->get('image.factory'),
108
      $container->get('entity.repository')
109
    );
110
  }
111
112
  /**
113
   * {@inheritdoc}
114
   */
115
  public static function defaultSettings() {
116
    return [
117
        'responsive_image_style' => '',
118
        'image_link' => FALSE,
119
      ] + parent::defaultSettings();
120
  }
121
122
  /**
123
   * {@inheritdoc}
124
   */
125
  public function settingsSummary() {
126
    $summary = [];
127
128
    $responsive_image_style = $this->responsiveImageStyleStorage->load($this->getSetting('responsive_image_style'));
129
    if ($responsive_image_style) {
130
      $summary[] = $this->t('Responsive image style: @responsive_image_style', ['@responsive_image_style' => $responsive_image_style->label()]);
131
132
      if ($this->getSetting('image_link')) {
133
        $summary[] = $this->t('Thumbnail is linked to entity');
134
      }
135
    }
136
    else {
137
      $summary[] = $this->t('Please select a responsive image style.');
138
    }
139
140
    return $summary;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $summary; (Drupal\Core\StringTranslation\TranslatableMarkup[]) is incompatible with the return type declared by the interface Drupal\Core\Field\Format...erface::settingsSummary of type string[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
141
  }
142
143
  /**
144
   * {@inheritdoc}
145
   */
146
  public function settingsForm(array $form, FormStateInterface $form_state) {
147
    $responsive_image_options = [];
148
    $responsive_image_styles = $this->responsiveImageStyleStorage->loadMultiple();
149
    if ($responsive_image_styles && !empty($responsive_image_styles)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $responsive_image_styles of type Drupal\Core\Entity\EntityInterface[] 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...
150
      foreach ($responsive_image_styles as $machine_name => $responsive_image_style) {
151
        if ($responsive_image_style->hasImageStyleMappings()) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Drupal\Core\Entity\EntityInterface as the method hasImageStyleMappings() does only exist in the following implementations of said interface: Drupal\responsive_image\...ty\ResponsiveImageStyle.

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...
152
          $responsive_image_options[$machine_name] = $responsive_image_style->label();
153
        }
154
      }
155
    }
156
157
    $elements['responsive_image_style'] = [
0 ignored issues
show
Coding Style Comprehensibility introduced by
$elements was never initialized. Although not strictly required by PHP, it is generally a good practice to add $elements = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
158
      '#title' => $this->t('Responsive image style'),
159
      '#type' => 'select',
160
      '#default_value' => $this->getSetting('responsive_image_style'),
161
      '#required' => TRUE,
162
      '#options' => $responsive_image_options,
163
    ];
164
165
    $elements['image_link'] = [
166
      '#title' => $this->t('Link image to content'),
167
      '#type' => 'checkbox',
168
      '#default_value' => $this->getSetting('image_link'),
169
    ];
170
171
    return $elements;
172
  }
173
174
  /**
175
   * {@inheritdoc}
176
   */
177
  public function viewElements(FieldItemListInterface $items, $langcode) {
178
    $elements = [];
179
180
    $entity = $items->getEntity();
181
    if ($file = $this->getFileFromPanelizedEntity($entity)) {
182
      if (($image = $this->imageFactory->get($file->getFileUri())) && $image->isValid()) {
183
        $item = new \stdClass();
184
        $item->width = $image->getWidth();
185
        $item->height = $image->getHeight();
186
        $item->alt = '';
187
        $item->title = $file->getFilename();
188
        $item->entity = $file;
189
190
        $url = NULL;
191
        if ($this->getSetting('image_link')) {
192
          $entity = $items->getEntity();
193
          if (!$entity->isNew()) {
194
            $url = $entity->toUrl();
195
          }
196
        }
197
198
        $elements = [
199
          '#theme' => 'responsive_image_formatter',
200
          '#item' => $item,
201
          '#responsive_image_style_id' => $this->getSetting('responsive_image_style'),
202
          '#url' => $url,
203
        ];
204
      }
205
    }
206
207
    return $elements;
208
  }
209
210
  /**
211
   * Finds the first file from a given Panelized entity.
212
   *
213
   * @param \Drupal\Core\Entity\EntityInterface|FieldableEntityInterface $entity
214
   *   A Panelized entity.
215
   * @return \Drupal\file\FileInterface|bool
216
   *   The first file displayed in a given display, or FALSE if none was found.
217
   */
218
  protected function getFileFromPanelizedEntity($entity) {
219
    if ($display = $this->panelizer->getPanelsDisplay($entity, 'full')) {
0 ignored issues
show
Compatibility introduced by
$entity of type object<Drupal\Core\Entity\EntityInterface> is not a sub-type of object<Drupal\Core\Entit...eldableEntityInterface>. It seems like you assume a child interface of the interface Drupal\Core\Entity\EntityInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
220
      $regions = $display->getRegionAssignments();
221
      foreach ($regions as $region => $blocks) {
222
        foreach ($blocks as $block) {
223
          if ($block instanceof BlockContentBlock) {
224
            $uuid = $block->getDerivativeId();
225
            /** @var \Drupal\block_content\BlockContentInterface $block_content */
226
            $block_content = $this->entityRepository->loadEntityByUuid('block_content', $uuid);
227
            if ($file = $this->getFileFromEntity($block_content)) {
228
              return $file;
229
            }
230
          }
231
          else if ($block instanceof EntityField) {
232
            list (, $field_name) = explode(':', $block->getDerivativeId());
233
            if ($entity->hasField($field_name) && $field = $entity->get($field_name)) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Drupal\Core\Entity\EntityInterface as the method hasField() does only exist in the following implementations of said interface: Drupal\Core\Entity\ContentEntityBase, Drupal\Core\Entity\RevisionableContentEntityBase, Drupal\Tests\Core\Entity\EntityManagerTestEntity, Drupal\aggregator\Entity\Feed, Drupal\aggregator\Entity\Item, Drupal\block_content\Entity\BlockContent, Drupal\comment\Entity\Comment, Drupal\contact\Entity\Message, Drupal\content_moderatio...\ContentModerationState, Drupal\content_translati...estTranslatableNoUISkip, Drupal\content_translati...yTestTranslatableUISkip, Drupal\crop\Entity\Crop, Drupal\devel_entity_test...evelEntityTestCanonical, Drupal\devel_entity_test...ity\DevelEntityTestEdit, Drupal\devel_entity_test...\DevelEntityTestNoLinks, Drupal\entity\Revision\R...onableContentEntityBase, Drupal\entity_composite_...stCompositeRelationship, Drupal\entity_gallery\Entity\EntityGallery, Drupal\entity_module_test\Entity\EnhancedEntity, Drupal\entity_test\Entity\EntityTest, Drupal\entity_test\Entity\EntityTestAdminRoutes, Drupal\entity_test\Entit...ityTestBaseFieldDisplay, Drupal\entity_test\Entity\EntityTestCache, Drupal\entity_test\Entit...TestCompositeConstraint, Drupal\entity_test\Entit...TestConstraintViolation, Drupal\entity_test\Entity\EntityTestConstraints, Drupal\entity_test\Entity\EntityTestDefaultAccess, Drupal\entity_test\Entity\EntityTestDefaultValue, Drupal\entity_test\Entity\EntityTestFieldMethods, Drupal\entity_test\Entity\EntityTestFieldOverride, Drupal\entity_test\Entity\EntityTestLabel, Drupal\entity_test\Entity\EntityTestLabelCallback, Drupal\entity_test\Entity\EntityTestMul, Drupal\entity_test\Entity\EntityTestMulChanged, Drupal\entity_test\Entit...tityTestMulDefaultValue, Drupal\entity_test\Entity\EntityTestMulLangcodeKey, Drupal\entity_test\Entity\EntityTestMulRev, Drupal\entity_test\Entity\EntityTestMulRevChanged, Drupal\entity_test\Entit...vChangedWithRevisionLog, Drupal\entity_test\Entity\EntityTestMulRevPub, Drupal\entity_test\Entit...TestMultiValueBasefield, Drupal\entity_test\Entity\EntityTestNew, Drupal\entity_test\Entity\EntityTestNoBundle, Drupal\entity_test\Entity\EntityTestNoId, Drupal\entity_test\Entity\EntityTestNoLabel, Drupal\entity_test\Entity\EntityTestNoUuid, Drupal\entity_test\Entity\EntityTestRev, Drupal\entity_test\Entity\EntityTestStringId, Drupal\entity_test\Entity\EntityTestViewBuilder, Drupal\entity_test\Entity\EntityTestWithBundle, Drupal\entity_test\Entit...tityTestWithRevisionLog, Drupal\entity_test_update\Entity\EntityTestUpdate, Drupal\file\Entity\File, Drupal\file_entity\Entity\FileEntity, Drupal\inline_entity_for...EntityTestWithoutBundle, Drupal\language_test\Entity\NoLanguageEntityTest, Drupal\media_entity\Entity\Media, Drupal\menu_link_content\Entity\MenuLinkContent, Drupal\message\Entity\Message, Drupal\migrate_entity_te...tity\StringIdEntityTest, Drupal\moderation_note\Entity\ModerationNote, Drupal\node\Entity\Node, Drupal\paragraphs\Entity\Paragraph, Drupal\scheduled_updates\Entity\ScheduledUpdate, Drupal\search_api\Entity\Task, Drupal\shortcut\Entity\Shortcut, Drupal\simple_oauth\Authentication\TokenAuthUser, Drupal\simple_oauth\Entity\Oauth2Client, Drupal\simple_oauth\Entity\Oauth2Token, Drupal\taxonomy\Entity\Term, Drupal\user\Entity\User.

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...
Bug introduced by
It seems like you code against a concrete implementation and not the interface Drupal\Core\Entity\EntityInterface as the method get() does only exist in the following implementations of said interface: Drupal\Core\Config\Entity\ConfigEntityBase, Drupal\Core\Config\Entity\ConfigEntityBundleBase, Drupal\Core\Datetime\Entity\DateFormat, Drupal\Core\Entity\ContentEntityBase, Drupal\Core\Entity\EntityDisplayBase, Drupal\Core\Entity\EntityDisplayModeBase, Drupal\Core\Entity\Entity\EntityFormDisplay, Drupal\Core\Entity\Entity\EntityFormMode, Drupal\Core\Entity\Entity\EntityViewDisplay, Drupal\Core\Entity\Entity\EntityViewMode, Drupal\Core\Entity\RevisionableContentEntityBase, Drupal\Core\Field\Entity\BaseFieldOverride, Drupal\Core\Field\FieldConfigBase, Drupal\Tests\Core\Config...seWithPluginCollections, Drupal\Tests\Core\Config...tyWithPluginCollections, Drupal\Tests\Core\Entity\EntityManagerTestEntity, Drupal\Tests\search\Unit\TestSearchPage, Drupal\aggregator\Entity\Feed, Drupal\aggregator\Entity\Item, Drupal\block\Entity\Block, Drupal\block_content\Entity\BlockContent, Drupal\block_content\Entity\BlockContentType, Drupal\comment\Entity\Comment, Drupal\comment\Entity\CommentType, Drupal\config_test\Entity\ConfigQueryTest, Drupal\config_test\Entity\ConfigTest, Drupal\contact\Entity\ContactForm, Drupal\contact\Entity\Message, Drupal\content_moderatio...\ContentModerationState, Drupal\content_translati...estTranslatableNoUISkip, Drupal\content_translati...yTestTranslatableUISkip, Drupal\crop\Entity\Crop, Drupal\crop\Entity\CropType, Drupal\ctools_wizard_tes...ity\ExampleConfigEntity, Drupal\devel_entity_test...evelEntityTestCanonical, Drupal\devel_entity_test...ity\DevelEntityTestEdit, Drupal\devel_entity_test...\DevelEntityTestNoLinks, Drupal\editor\Entity\Editor, Drupal\embed\Entity\EmbedButton, Drupal\entity\Revision\R...onableContentEntityBase, Drupal\entity_browser\Entity\EntityBrowser, Drupal\entity_composite_...stCompositeRelationship, Drupal\entity_gallery\Entity\EntityGallery, Drupal\entity_gallery\Entity\EntityGalleryType, Drupal\entity_module_test\Entity\EnhancedEntity, Drupal\entity_module_tes...ty\EnhancedEntityBundle, Drupal\entity_test\Entity\EntityTest, Drupal\entity_test\Entity\EntityTestAdminRoutes, Drupal\entity_test\Entit...ityTestBaseFieldDisplay, Drupal\entity_test\Entity\EntityTestBundle, Drupal\entity_test\Entity\EntityTestCache, Drupal\entity_test\Entit...TestCompositeConstraint, Drupal\entity_test\Entit...TestConstraintViolation, Drupal\entity_test\Entity\EntityTestConstraints, Drupal\entity_test\Entity\EntityTestDefaultAccess, Drupal\entity_test\Entity\EntityTestDefaultValue, Drupal\entity_test\Entity\EntityTestFieldMethods, Drupal\entity_test\Entity\EntityTestFieldOverride, Drupal\entity_test\Entity\EntityTestLabel, Drupal\entity_test\Entity\EntityTestLabelCallback, Drupal\entity_test\Entity\EntityTestMul, Drupal\entity_test\Entity\EntityTestMulChanged, Drupal\entity_test\Entit...tityTestMulDefaultValue, Drupal\entity_test\Entity\EntityTestMulLangcodeKey, Drupal\entity_test\Entity\EntityTestMulRev, Drupal\entity_test\Entity\EntityTestMulRevChanged, Drupal\entity_test\Entit...vChangedWithRevisionLog, Drupal\entity_test\Entity\EntityTestMulRevPub, Drupal\entity_test\Entit...TestMultiValueBasefield, Drupal\entity_test\Entity\EntityTestNew, Drupal\entity_test\Entity\EntityTestNoBundle, Drupal\entity_test\Entity\EntityTestNoId, Drupal\entity_test\Entity\EntityTestNoLabel, Drupal\entity_test\Entity\EntityTestNoUuid, Drupal\entity_test\Entity\EntityTestRev, Drupal\entity_test\Entity\EntityTestStringId, Drupal\entity_test\Entity\EntityTestViewBuilder, Drupal\entity_test\Entity\EntityTestWithBundle, Drupal\entity_test\Entit...tityTestWithRevisionLog, Drupal\entity_test_update\Entity\EntityTestUpdate, Drupal\facets\Entity\Facet, Drupal\facets\Entity\FacetSource, Drupal\facets_summary\Entity\FacetsSummary, Drupal\features\Entity\FeaturesBundle, Drupal\field\Entity\FieldConfig, Drupal\field\Entity\FieldStorageConfig, Drupal\field_layout\Enti...LayoutEntityFormDisplay, Drupal\field_layout\Enti...LayoutEntityViewDisplay, Drupal\file\Entity\File, Drupal\file_entity\Entity\FileEntity, Drupal\file_entity\Entity\FileType, Drupal\filter\Entity\FilterFormat, Drupal\image\Entity\ImageStyle, Drupal\inline_entity_for...EntityTestWithoutBundle, Drupal\language\Entity\ConfigurableLanguage, Drupal\language\Entity\ContentLanguageSettings, Drupal\language_test\Entity\NoLanguageEntityTest, Drupal\lightning_core\Entity\EntityFormMode, Drupal\lightning_core\Entity\EntityViewMode, Drupal\lightning_core\Entity\Role, Drupal\media_entity\Entity\Media, Drupal\media_entity\Entity\MediaBundle, Drupal\menu_link_content\Entity\MenuLinkContent, Drupal\message\Entity\Message, Drupal\message\Entity\MessageTemplate, Drupal\metatag\Entity\MetatagDefaults, Drupal\migrate_entity_te...tity\StringIdEntityTest, Drupal\migrate_plus\Entity\Migration, Drupal\migrate_plus\Entity\MigrationGroup, Drupal\moderation_note\Entity\ModerationNote, Drupal\module_installer_...t\Entity\TestConfigType, Drupal\node\Entity\Node, Drupal\node\Entity\NodeType, Drupal\page_manager\Entity\Page, Drupal\page_manager\Entity\PageVariant, Drupal\paragraphs\Entity\Paragraph, Drupal\paragraphs\Entity\ParagraphsType, Drupal\pathauto\Entity\PathautoPattern, Drupal\rdf\Entity\RdfMapping, Drupal\responsive_image\...ty\ResponsiveImageStyle, Drupal\responsive_preview\Entity\Device, Drupal\rest\Entity\RestResourceConfig, Drupal\rules\Entity\ReactionRuleConfig, Drupal\rules\Entity\RulesComponentConfig, Drupal\scheduled_updates\Entity\ScheduledUpdate, Drupal\scheduled_updates...ity\ScheduledUpdateType, Drupal\search\Entity\SearchPage, Drupal\search_api\Entity\Index, Drupal\search_api\Entity\Server, Drupal\search_api\Entity\Task, Drupal\search_api\UnsavedIndexConfiguration, Drupal\search_api_autocomplete\Entity\Search, Drupal\shortcut\Entity\Shortcut, Drupal\shortcut\Entity\ShortcutSet, Drupal\simple_oauth\Authentication\TokenAuthUser, Drupal\simple_oauth\Entity\Oauth2Client, Drupal\simple_oauth\Entity\Oauth2Token, Drupal\simple_oauth\Entity\Oauth2TokenType, Drupal\system\Entity\Action, Drupal\system\Entity\Menu, Drupal\taxonomy\Entity\Term, Drupal\taxonomy\Entity\Vocabulary, Drupal\tour\Entity\Tour, Drupal\user\Entity\Role, Drupal\user\Entity\User, Drupal\views\Entity\View, Drupal\views_ui\ViewUI, Drupal\workbench_email\Entity\Template, Drupal\workbench_moderation\Entity\ModerationState, Drupal\workbench_moderat...derationStateTransition, Drupal\workflows\Entity\Workflow.

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...
234
              if ($file = $this->getFileFromField($field)) {
235
                return $file;
236
              }
237
            }
238
          }
239
        }
240
      }
241
    }
242
243
    return FALSE;
244
  }
245
246
  /**
247
   * Gets the first file present in a given entity.
248
   *
249
   * @param \Drupal\Core\Entity\EntityInterface|FieldableEntityInterface $entity
250
   *   The entity you want to find a file for.
251
   * @return \Drupal\file\FileInterface|bool
252
   *   The first file found for a given entity, or FALSE
253
   */
254
  protected function getFileFromEntity(FieldableEntityInterface $entity) {
255
    foreach ($entity->getFields() as $field) {
256
      if ($file = $this->getFileFromField($field)) {
257
        return $file;
258
      }
259
    }
260
261
    return FALSE;
262
  }
263
264
  /**
265
   * Gets the first file from a given field.
266
   *
267
   * If the field is a Media reference, recursion is used to find nested
268
   * file fields.
269
   *
270
   * @param \Drupal\Core\Field\FieldItemListInterface $field
271
   * @return \Drupal\file\FileInterface|bool
272
   */
273
  protected function getFileFromField(FieldItemListInterface $field) {
274
    static $depth = 0;
275
    $depth++;
276
277
    $return = FALSE;
278
279
    if ($field instanceof EntityReferenceFieldItemList) {
280
      $referenced_entities = $field->referencedEntities();
281
      $referenced_entity = reset($referenced_entities);
282
      if ($referenced_entity instanceof FileInterface) {
283
        $return = $referenced_entity;
284
      }
285
      else if ($referenced_entity instanceof MediaInterface) {
286
        if ($depth < self::DEPTH_LIMIT && $file = $this->getFileFromEntity($referenced_entity)) {
287
          $return =  $file;
288
        }
289
      }
290
    }
291
292
    $depth--;
293
    return $return;
294
  }
295
296
}
297