Skip to content

Custom blocks

Most blocks in this module extend \Drupal\o365\Block\O365BlockBase or \Drupal\o365\Block\O365UncachedBlockBase. Use them for your own blocks too. They save you the constructor boilerplate and, more importantly, they get the access check right.

What the base class does for you

O365BlockBase injects the Graph service and exposes it as $this->graphService, so you do not have to write a create() method.

It also overrides access(). The block is forbidden for anonymous users, and forbidden for authenticated users who are not linked to a Microsoft 365 account. That second check is the one people forget when they roll their own: a local editor account that never logged in through Microsoft has no token, and every Graph call for that user comes back empty. Without the check you get a block full of nothing instead of no block at all.

A block

<?php

namespace Drupal\my_module\Plugin\Block;

use Drupal\o365\Block\O365BlockBase;

/**
 * Lists the Microsoft 365 groups of the current user.
 *
 * @Block(
 *   id = "my_module_my_groups",
 *   admin_label = @Translation("My Microsoft 365 groups"),
 * )
 */
class MyGroupsBlock extends O365BlockBase {

  /**
   * {@inheritdoc}
   */
  public function build() {
    $response = $this->graphService->getGraphData('/me/memberOf?$select=id,displayName');

    $items = [];
    foreach ($response['value'] ?? [] as $group) {
      $items[] = $group['displayName'];
    }

    return [
      '#theme' => 'item_list',
      '#items' => $items,
      '#cache' => [
        'contexts' => ['user'],
        'max-age' => 900,
      ],
    ];
  }

}

Caching

This is the part that bites.

The base class adds no cache context

A block that renders per user data will happily be cached and served to the next visitor. Either set 'contexts' => ['user'] on the render array yourself, as above, or extend O365UncachedBlockBase instead.

O365UncachedBlockBase is O365BlockBase plus UncacheableDependencyTrait. It makes the block uncacheable, full stop, which means a Graph call on every request that renders it. That is the right answer for something like presence, which changes by the minute. It is the wrong answer for a list of groups, which changes twice a year. Pick deliberately; an uncacheable block in a sidebar on every page is a noticeable amount of traffic to Microsoft.

Access on top of the base check

If your block needs its own condition, call the parent and combine the results rather than replacing them:

public function access(AccountInterface $account, $return_as_object = FALSE) {
  $access = parent::access($account, TRUE)
    ->andIf(AccessResult::allowedIfHasPermission($account, 'view my groups block'));

  return $return_as_object ? $access : $access->isAllowed();
}