Change record status: 
Project: 
Introduced in branch: 
8.8.x
Introduced in version: 
8.8.0
Description: 

Starting with Drupal 8.8.0, custom URL aliases are now implemented via a new path_alias revisionable content entity type provided by the new (required) path_alias module (see related change record).

The path.alias_storage service has been kept in place for backwards compatibility, and its hook have been deprecated.

The following code changes are recommended in order to fully utilize the new system and prepare your code for Drupal 9:

Use path alias objects instead of path arrays

Given a deprecated path alias array with the following structure:

$alias = [
  'source' => '/a/system/path',
  'alias' => '/a-path-alias',
  'langcode' => 'en',
];

Using a new path alias entity object:

  • To create new alias:
    $path_alias = \Drupal::entityTypeManager()->getStorage('path_alias')->create([
      'path' => '/a/system/path',
      'alias' => '/a-path-alias',
      'langcode' => Language::LANGCODE_NOT_SPECIFIED,
    ]);
    $path_alias->save();
  • $alias['source'] becomes $path_alias->getPath()
  • $alias['alias'] becomes $path_alias->getAlias()
  • $alias['langcode'] becomes $path_alias->language()->getId()

 

Hooks

Regular entity hooks should be used instead of the old path-specific ones:

Before:

hook_path_insert($path)
hook_path_update($path)
hook_path_delete($path)

After:

hook_entity_insert(Drupal\Core\Entity\EntityInterface $entity)
hook_path_alias_insert(Drupal\path_alias\PathAliasInterface $path_alias)

hook_entity_update(Drupal\Core\Entity\EntityInterface $entity)
hook_path_alias_update(Drupal\path_alias\PathAliasInterface $path_alias)

hook_entity_delete(Drupal\Core\Entity\EntityInterface $entity)
hook_path_alias_delete(Drupal\path_alias\PathAliasInterface $path_alias)

The deprecated hook implementations from Drupal core have been converted as follows:

menu_link_content_path_insert() -> menu_link_content_path_alias_insert()
menu_link_content_path_update() -> menu_link_content_path_alias_update()
menu_link_content_path_delete() -> menu_link_content_path_alias_delete()

system_path_insert() -> PathAlias::postSave()
system_path_update() -> PathAlias::postSave()
system_path_delete() -> PathAlias::postDelete()

Alias storage

Before:

\Drupal::service('path.alias_storage')->load($conditions)
\Drupal::service('path.alias_storage')->save($source, $alias, $langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED, $pid = NULL)
\Drupal::service('path.alias_storage')->delete($conditions)

After:

\Drupal::entityTypeManager()->getStorage('path_alias')->load($id)
\Drupal::entityTypeManager()->getStorage('path_alias')->loadByProperties(array $values)
\Drupal::entityTypeManager()->getStorage('path_alias')->save($path_alias)
\Drupal::entityTypeManager()->getStorage('path_alias')->delete([$path_alias])

Forms

The following table lists all the previous form classes and their IDs, along with their replacements.

Previous form class Previous form ID   New form class New form ID
Drupal\path\Form\AddForm path_admin_add -> Drupal\path\PathAliasForm path_alias_form
Drupal\path\Form\EditForm path_admin_edit -> Drupal\path\PathAliasForm path_alias_form
Drupal\path\Form\DeleteForm path_alias_delete -> Drupal\Core\Entity\ContentEntityDeleteForm path_alias_delete_form
Drupal\path\Form\PathFormBase N/A -> Drupal\path\PathAliasForm N/A

Custom code needs to be updated for any hook_form_alter() or hook_form_FORM_ID_alter() implementations that were using the previous form IDs. For example, a hook_form_path_admin_add_alter() implementation of hook_form_FORM_ID_alter() for the path_admin_add form ID needs to be renamed to hook_form_path_alias_form_alter().

Additionally, the following routes have been deprecated and replaced by generic entity routes:

Previous route name New route name
path.admin_add entity.path_alias.add_form
path.admin_edit entity.path_alias.edit_form
path.delete entity.path_alias.delete_form
path.admin_overview entity.path_alias.collection

The path.admin_overview_filter route has also been deprecated, and it's functionality has been folded into the generic entity.path_alias.collection route, by using a search request query parameter.

Migrations

The custom url_alias (\Drupal\path\Plugin\migrate\destination\UrlAlias) migrate destination plugin has been deprecated in favor of the generic entity-based entity:path_alias destination plugin. The Drupal 6 and 7 source plugins have also been updated accordingly.

Important notice for contributed and custom module's Kernel tests:

In order to allow Kernel tests to work on both Drupal 8.8 as well as previous minor releases (e.g. 8.7 and 8.6), the path_alias entity type schema needs to specifically installed in the test's setUp() method, like so:

    if (\Drupal::entityTypeManager()->hasDefinition('path_alias')) {
      $this->installEntitySchema('path_alias');
    }

Important notice for contributed and custom module's Upgrade tests:

In order to allow Upgrade tests to work on both Drupal 8.8 as well as previous minor releases (e.g. 8.7 and 8.6), your test cannot call drupalGet or drupalLogin before the runUpdates call if your dump is created with a Core version before 8.8.

Impacts: 
Site builders, administrators, editors
Module developers
Distribution developers
Updates Done (doc team, etc.)
Online documentation: 
Not done
Theming guide: 
Not done
Module developer documentation: 
Not done
Examples project: 
Not done
Coder Review: 
Not done
Coder Upgrade: 
Not done
Other: 
Other updates done

Comments

m4olivei’s picture

Just want to flag that the alias storage moves in this update from the url_alias table to path_alias and path_alias_revision tables. The old path_alias table is either dropped or renamed for backup purposes. If you have code querying directly against url_alias table, that will need to be updated.

ccshannon’s picture

Thanks for this. It's helpful, but I ran into an issue following one of the examples.

if you do this example from AliasStorage:
$pathAlias = \Drupal::entityTypeManager()->getStorage('path_alias')->load($id) // FYI, that $id is the 'pid' value in a returned path array, or the ->id() in a path object

then do this ...

\Drupal::entityTypeManager()->getStorage('path_alias')->delete($pathAlias)

... you will get a WSOD/Fatal error/500/etc.

To delete, wrap $pathAlias in an array:

\Drupal::entityTypeManager()->getStorage('path_alias')->delete([$pathAlias]) // See the square brackets around '$pathAlias'?

I almost went mad trying to delete a path. Don't go mad, wrap it in an array. It works.

amateescu’s picture

I updated the change record to use an array for the delete() code example :)

danflanagan8’s picture

This change also introduced a very useful migrate process plugin called null_coalesce (NullCoalesce). It returns the first non null value from a source array. It's great for titles where a missing value would result in a sql error. The same code can give stubs a meaningful title (the id) instead of a weird hash.

title:
  plugin: null_coalesce
  source:
    - preferred_title
    - backup_title
    - id