Change record status: 
Project: 
Introduced in branch: 
8.x
Description: 

drupal_set_title()

Titles on routes now can be set on various ways, depending on your usecase.
Previously just drupal_set_title() was called in whatever place. The following usecases exist:

  1. Static title:
    For static titles you set a '_title' on the routing definition:
    block.admin_add:
      path: '/admin/structure/block/add/{plugin_id}/{theme}'
      defaults:
        _controller: '\Drupal\block\Controller\BlockAddController::blockAddConfigureForm'
        _title: 'Configure block'
      requirements:
        _permission: 'administer blocks'
    
  2. Dynamic title:
    If you write a controller and you need a dynamic title, for example depending on the site configuration, use _title_callback in the route defaults.
    mymodule.test:
      path: '/mymodule/test'
      defaults:
        _controller: '\Drupal\mymodule\Controller\Test::getContent'
        _title_callback: '\Drupal\mymodule\Controller\Test::getTitle'
    
    class Test {
    
      /**
       * Returns a page title.
       */
      public function getTitle() {
        return  'Foo: ' . \Drupal::config('system.site')->get('name');
      }
    
      /**
       * Returns a page render array.
       */
      public function getContent() {
        $build = array();
        $build['#markup'] = 'Hello Drupal';
        return $build;
      }
    }
    
  3. Final title override:

    If you write a controller and you need to override the title from the route, you can return #title in the render array. This should generally be avoided, since the title for the page when fully rendered could be different from the title in other contexts (like in the breadcrumb).

    class Test {
    
      /**
       * Renders a page with a title.
       *
       * @return array
       *   A render array as expected by drupal_render()
       */
      public function getContentWithTitle() {
        $build = array();
        $build['#markup'] = 'Hello Drupal';
        $build['#title'] = 'Foo: ' . \Drupal::config('system.site')->get('name');
    
        return $build;
      }
    
    }
    

Output flag of drupal_set_title #

The output validation in Drupal 8 is opposite to Drupal 7. We have to explicitly specify PASS_THROUGH and CHECK_PLAIN is by default in Drupal 7, whereas in Drupal 8 the situation is different. Output is autoescaped unless it is marked safe. Both t() and new FormattableMarkup return objects that will not be autoescaped.

Drupal 7:

  drupal_set_title(t('Add new shortcut'));

Drupal 8:

  $form['#title'] = $this->t('Add new shortcut');

Drupal 7:

  drupal_set_title(t("%name block", array('%name' => $info[$block->delta]['info'])), PASS_THROUGH);

Drupal 8

  $form['#title'] = $this->t("'%name' block", array('%name' => $info[$block->delta]['info']));

drupal_get_title()

As titles on routes now can be set on various ways (see above), drupal_get_title() has been removed. In its place you should call the title_resolver service.

Drupal 7:

  $title = drupal_get_title();

Drupal 8:

  $request = \Drupal::request();
  $route_match = \Drupal::routeMatch();
  $title = \Drupal::service('title_resolver')->getTitle($request, $route_match->getRouteObject());

Comments

pownraj’s picture

\Drupal::config()->get('system.site')->get('name'); doesn't work anymore.

You have to include the parameters within config itself.

\Drupal::config('system.site')->get('name');

Torenware’s picture

This really needs to be documented, and after spending some quality time in the debugger, I can tell you how this works.

If your routing file says something like this:

mymodule.item.edit:
  path: '/mymodule/item/{my_item_object}'
  defaults:
    _controller: '\Drupal\mymodule\Controller\Test::getContent'
    _title_callback: '\Drupal\mymodule\Controller\Test::getTitle'

then you should declare getTitle like this:


class Test ... {

...

  /**
    * My title callback
    * @param MyItemObjectInterface $my_item_object
    * @return string
    */
   public function getTitle(MyItemObjectInterface $my_item_object) {
      //format your string using your object..
      return "formatted title"
   }
..

}

where the parameter name $my_item_object is an exact match for the parameter you used in your route's path, and MyItemObjectInterface is the appropriate class for an object that the routing system will load when it sees the parameter {my_item_object} in the route.

I don't think this is directly documented anywhere else; I figured it out by seeing what the Symfony routing code does.

Rob Thorne
Torenware Networks

norman.lol’s picture

Thanks for documenting this!

elek’s picture

I need the title in template_preprocess_page(), but have problems with views

This works to get the title but on views pages returns always the main language:

  $request = \Drupal::request();
  $route_match = \Drupal::routeMatch();
  $title = \Drupal::service('title_resolver')->getTitle($request, $route_match->getRouteObject());

Node-titles are correct in different languages.

Maby it's because of the routeMatch()
I also did not manage to generate different URLs from views in different languages.

Using Drupal 8.1.3

laura.gates’s picture

In case someone comes across this for D9 issues the below snippet worked as a fix for me:

  $route = \Drupal::routeMatch()->getRouteObject();
  $request = \Drupal::request();
  if ($route) {
    $page_title = \Drupal::service('title_resolver')->getTitle($request, $route);
  };
taggartj’s picture

$build['title'][0]['#context']['value'] = 'changed';
crantisz’s picture

drupal_set_title() - is too easy! I'm happy that you remove this!

serverjohn’s picture

After much scouring of the web the below works to change the view title both <h1> and <head><title>:

Create a custom module and place the following in the .module file

/**
* Implements hook_views_post_render().
*/
function mymodule_views_post_render(Drupal\views\ViewExecutable $view) {
  if ($view->element['#view_id'] == 'id_of_view') {
    if ($view->element['#display_id'] == 'id_of_display') {
      $title = 'test';
      $view->setTitle($title); // Sets h1
      $route = \Drupal::routeMatch()->getCurrentRouteMatch()->getRouteObject();
      $route->setDefault('_title_callback', function() use ($title) {
        return $title; // sets <head><title>
      }); 
    }
  }
}
jons’s picture

very helpful @serverjohn!

fanton’s picture

Thanks, @serverjohn!

liquidcms’s picture

Lots on getTitle() here. Nothing about dynamically setting a title.

x0r1x’s picture

Found a solution, works with Drupal-10.1

public function setPageTitle($title) {
        $request = \Drupal::request();
        if ($route = $request->attributes->get(\Drupal\Core\Routing\RouteObjectInterface::ROUTE_OBJECT)) {
            $route->setDefault('_title', $title);
        }
    }