Better static analysis with entity type storages in phpstan-drupal 1.10

  • On analysis level 2, PHPStan would consider $entity->get('my_field')->value invalid but $entity->get('my_field')->first()->value valid. The former is allowed due field item list classes defaulting to the first value when using the magically __get call.
  • When an entity query is executed, the return type may be an int or array of entities, depending on if the count method was called before execution, creating a count query. PHPStan read the return type from the methods docblock, and that was it. Now, it will properly return int, array<int, string> for content entities, and array<string, string> for configuration entities.
  • Ensure that entity storage methods return the appropriate entity class if it has been defined in the configuration. Before this release, phpstan-drupal only allowed configuring the entity storage class to improve static analysis. With 1.1.0, the entity class can also be defined. If a storage class is not provided, but the entity class is, it will infer the appropriate default storage based on if it is a content or configuration entity.
\Drupal::entityTypeManager()->getStorage('node');
\Drupal::entityTypeManager()->getStorage('user');
\Drupal::entityTypeManager()->getStorage('block');

Configuring entity mapping

parameters:
drupal:
entityTypeStorageMapping:
node: Drupal\node\NodeStorage
taxonomy_term: Drupal\taxonomy\TermStorage
user: Drupal\user\UserStorage
parameters:
drupal:
entityMapping:
node:
class: Drupal\node\Entity\Node
storage: Drupal\node\NodeStorage
taxonomy_term:
class: Drupal\taxonomy\Entity\Term
storage: Drupal\taxonomy\TermStorage
user:
class: Drupal\user\Entity\User
storage: Drupal\user\UserStorage
block:
class: Drupal\block\Entity\Block

How does the entity type storage analysis work?

assertType('Drupal\node\NodeStorage', $etm->getStorage('node'));
assertType('Drupal\user\UserStorage', $etm->getStorage('user'));
assertType('Drupal\taxonomy\TermStorage', $etm->getStorage('taxonomy_term'));
assertType('Drupal\Core\Entity\EntityStorageInterface', $etm->getStorage('search_api_index'));
assertType('Drupal\Core\Config\Entity\ConfigEntityStorage', $etm->getStorage('block'));
assertType(
'array<string, string>',
\Drupal::entityTypeManager()->getStorage('block')->getQuery()
->execute()
);
assertType(
'array<int, string>',
\Drupal::entityTypeManager()->getStorage('node')->getQuery()
->accessCheck(TRUE)
->execute()
);
assertType(
'int',
\Drupal::entityTypeManager()->getStorage('node')->getQuery()
->accessCheck(TRUE)
->count()
->execute()
);
  • The execute method has a documented return type of int | array (source). By default, it returns an array of entity IDs, otherwise an integer for the count of entities when it is a county entity.
  • Content entities have serial identifiers (auto-incrementing). The result is an array keyed by the entity ID or revision ID and a value of its entity ID. As you may notice, the node query return type is array<int, string>. The keys are converted to integers by PHP, as their array keys. But! Numbers are not automatically cast to integers when retrieved from the database. So the IDs will be integer strings.
  • Configuration entities have string identifiers. The entity query will always be an array of configuration entity IDs, keyed by their ID as well. That is why it has the array<string, string> return type.
  • create – creates a new unsaved entity object.
  • load – loads an entity by its identifier
  • loadUnchanged – loads the entity by identifier directly from storage, bypassing any static cache
  • loadMultiple – loads multiple entities by their identifiers
  • loadByProperties – loads multiple entities based on properties, a shortcut for an entity query.
$nodeStorage = \Drupal::entityTypeManager()->getStorage('node');
assertType('Drupal\node\Entity\Node', $nodeStorage->create(['type' => 'page', 'title' => 'foo']));
assertType('Drupal\node\Entity\Node|null', $nodeStorage->load(42));
assertType('Drupal\node\Entity\Node|null', $nodeStorage->loadUnchanged('42'));
assertType('array<int, Drupal\node\Entity\Node>', $nodeStorage->loadMultiple([42, 29]));
assertType('array<int, Drupal\node\Entity\Node>', $nodeStorage->loadMultiple(NULL));
assertType('array<int, Drupal\node\Entity\Node>', $nodeStorage->loadByProperties([]));
$storage = \Drupal::entityTypeManager()->getStorage('unknown_entity_type_id');
assertType('Drupal\Core\Entity\EntityInterface', $storage->create(['name' => 'foo']));
assertType('Drupal\Core\Entity\EntityInterface|null', $storage->load(42)););
assertType('Drupal\Core\Entity\EntityInterface|null', $storage->loadUnchanged(42));
assertType('array<Drupal\Core\Entity\EntityInterface>', $storage->loadMultiple([42, 29]));
assertType('array<Drupal\Core\Entity\EntityInterface>', $storage->loadMultiple(NULL));
assertType('array<Drupal\Core\Entity\EntityInterface>', $storage->loadByProperties([]));

What would you like to see?

--

--

--

Open source developer, working with Drupal and building Drupal Commerce.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Leetcode patterns : Dynamic programming

Leetcode solutions : Dynamic programming #55 Jump game

Oracle Database in Docker Container

Hacking — Best OF Reverse Engineering — Part 8

How to install an SSL Certificate on Heroku?

Speechelo Review- Is Speechelo the best human-sounding voice-over?

Careem’s Recruitment CTF Challenge

Operational excellence easy as counting 1-2-3

CS373 Fall 2021: Entry #4

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Matt Glaman

Matt Glaman

Open source developer, working with Drupal and building Drupal Commerce.

More from Medium

Auto discovery of global commands in Drush

User data verification api with NIN/BVN/Phone No/Passport No using MyIdentityPass Api

Definite guide to setting up Sourcetree and GitHub on Mac (M1)

CI/CD configuration with GitLab Runner and pm2