How to Alter Node URL Alias Based on Taxonomy Term in Drupal 8

Recently I had to generate term-specific aliases (aliases that are different from the default alias pattern set for Article entities). This is how to do it:

1. Enable the Pathauto module
2. Set the default URL alias pattern for your content type in order to fire the hook
3. Implement hook_pathauto_alias_alter() in your .module file.

Example module structure:

mymodule/
  - mymodule.info.yml
  - mymodule.module
  - src/
    - ArticlePathAlias.php

I like to keep .module clean and simple and because of that I store the main logic in src/ArticlePathAlias.php file.

The mymodule.info.yml this is just a regular .info file.

4. Add the following to your mymodule.module file:

use Drupal\mymodule\ArticlePathAlias;

/**
 * Implements hook_pathauto_alias_alter().
 */
function mymodule_pathauto_alias_alter(&$alias, array &$context) {
  if ($new_alias = (new ArticlePathAlias())->generate($context)) {
    $alias = $new_alias;
  }
}

5. Add the following to your src/ArticlePathAlias.php file:

<?php

namespace Drupal\mymodule;

use Drupal\Component\Utility\Html;
use Drupal\taxonomy\Entity\Term;

/**
 * Generate URL aliases for articles.
 */
class ArticlePathAlias {

  protected $terms = [
    'Term name 1' => 'custom/alias',
    'Term name 2' => 'custom2/alias',
    'Term name 3' => 'custom3/alias',
  ];
  protected $pattern = '/%term%/%year%/%monthnum%/%day%/%postname%';

  public function generate($context) {
    if ($context['bundle'] === 'article' && ($context['op'] == 'insert' || $context['op'] == 'update')) {
      return $this->assembleAlias($context['data']['node']);
    }
  }

  protected function assembleAlias($entity) {
    $date = new \DateTime(date('c', $entity->getCreatedTime()));
    $parameters = [
      '%year%'     => $date->format('Y'),
      '%monthnum%' => $date->format('m'),
      '%day%'      => $date->format('d'),
      '%postname%' => Html::cleanCssIdentifier($entity->getTitle()),
      '%term%'     => $this->findTermAlias($entity),
    ];
    if (!empty($parameters['%term%'])) {
      return str_replace(array_keys($parameters), array_values($parameters), $this->pattern);
    }
  }

  protected function findTermAlias($entity) {
    // Make sure to change `field_keywords` to the field you would like to check.
    if ($keywords = $entity->get('field_keywords')->getValue()) {
      foreach ($keywords as $data) {
        $term = Term::load($data['target_id']);
        $name = $term->getName();
        if (in_array($name, array_keys($this->terms))) {
          return $this->terms[$name];
        }
      }
    }
  }

}

The code above will generate /%term%/%year%/%monthnum%/%day%/%postname% alias or (/custom/alias/2017/07/21/test-title) depending on the term.

Make sure you change field_keywords to your own taxonomy term reference field. Also change $context['bundle'] === 'article' to entity type that will trigger custom alias.