Drupal 8: How to Swap Out a Core Service
This article describes how to swap out a service in the BookManager Class which is found in the file core/modules/book/src/BookManager.php. This class provides the services that manage the book navigation menus.
When editing the order and titles of a book menu, I want the program to also display unpublished pages. My workflow when creating a new book is to brainstorm an outline by first creating a bunch of blank pages. I use the book menu editing page to arrange them into the correct order. By doing this I break the enormous task of writing a book into lots of individual pages.
The problem is that the core book module only displays published pages. I found the function that queries the database and realized all I need to do is comment out one line which sets a query condition for page status. But it's in core, and you don't hack core. Thanks to the wonders of Drupal 8 and services, I can easily swap out that service - and after a little research I found it's really pretty easy.
Below is the core function I want to swap out. It's found in the BookManager class in file core/modules/book/src/BookManager.php. I added a comment to the line I want to comment out.
public function bookTreeCheckAccess(&$tree, $node_links = []) {
if ($node_links) {
// @todo Extract that into its own method.
$nids = array_keys($node_links);
// @todo This should be actually filtering on the desired node status
// field language and just fall back to the default language.
$nids = \Drupal::entityQuery('node')
->condition('nid', $nids, 'IN')
->condition('status', 1) // If I comment out this line I get unpublished pages.
->execute();
foreach ($nids as $nid) {
foreach ($node_links[$nid] as $mlid => $link) {
$node_links[$nid][$mlid]['access'] = TRUE;
}
}
}
$this->doBookTreeCheckAccess($tree);
}
For this example we will create a new module called az_book. Create the directory modules/custom/az_book. Then create the az_book.info.yml file as follows:
name: AZ Book
type: module
description: Provide custom functionality to Book Menus
core: 8.x
package: AZ
Next look at the core/modules/book/book.services.yml file. Find the book.manager service and copy the lines into your az_book.services.yml file. Be sure to change the service name to az_book.manager and set the correct path to your class file:
services:
az_book.manager:
class: Drupal\az_book\AzBookManager
arguments: ['@entity.manager', '@string_translation', '@config.factory', '@book.outline_storage', '@renderer']
Now we create a service manager class which tells Drupal we are extending the core BookManager services with our own. Create the file modules/custom/az_book/src/AzServiceManager.php as follows:
<?php
namespace Drupal\az_book;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\DependencyInjection\ServiceProviderBase;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
/**
* Defines a book manager which extends the core BookManager class.
*/
class AzBookServiceProvider extends ServiceProviderBase {
/**
* {@inheritdoc}
*/
public function alter(ContainerBuilder $container) {
$definition = $container->getDefinition('book.manager');
$definition->setClass('Drupal\az_book\AzBookManager');
}
}
Finally create your new AzBookManager class as below. This class only needs the one function you are swapping out. It's identical to the core BookManager::bookTreeCheckAccess function except I've broken the query into separate statements so I can add an if statement to filter out unpublished pages only if this isn't the book menu edit page (route: book.admin_edit).
<?php
namespace Drupal\az_book;
use Drupal\book\BookManager;
// Override BookManager service.
// - Include unpublished books in book outline edit form.
class AzBookManager extends BookManager {
/**
* {@inheritdoc}
*
* Override core and Load books that are not published.
*/
public function bookTreeCheckAccess(&$tree, $node_links = []) {
if ($node_links) {
// @todo Extract that into its own method.
$nids = array_keys($node_links);
// @todo This should be actually filtering on the desired node status
// field language and just fall back to the default language.
$query = \Drupal::entityQuery('node')
->condition('nid', $nids, 'IN');
if (\Drupal::routeMatch()->getRouteName() != 'book.admin_edit') {
$query->condition('status', 1);
};
$nids = $query->execute();
foreach ($nids as $nid) {
foreach ($node_links[$nid] as $mlid => $link) {
$node_links[$nid][$mlid]['access'] = TRUE;
}
}
}
$this->doBookTreeCheckAccess($tree);
}
}
Clear cache and you're done.
There are many more services in the BookManager class. I didn't try it but I'm pretty sure you can swap out any of them using this method.
- Log in to post comments