You need to create this file inside your custom module: <my_module>.services.yml, for example mine is called maria_custom.services.yml and it looks like:
services:
maria_custom.service:
class: '\Drupal\maria_custom\MariaCustomService'
arguments: ['@entity_type.manager', '@entity_field.manager', '@database', '@config.factory', '@logger.factory', '@date.formatter', '@messenger', '@session', '@user.data', '@current_user']
A Service is the class MariaCustomService which is using other services such as Entity Type Manager, Entity Field Manager, Database, Config Factory, Logger Factory, Date Formatter, Messenger, Session, User Data and Current UserAccount. This practise of re-use existing Services is called Dependency Injection and in our Custom Service class the __construct method looks like:
public function __construct(
EntityTypeManagerInterface $entity_type_manager,
EntityFieldManagerInterface $entity_field_manager,
Connection $db_connection,
ConfigFactoryInterface $config_factory,
LoggerChannelFactoryInterface $logger,
DateFormatter $date_formatter,
MessengerInterface $messenger,
Session $session,
UserData $user_data,
AccountInterface $account)
{
$this->entityTypeManager = $entity_type_manager;
$this->entityFieldManager = $entity_field_manager;
$this->database = $db_connection;
$this->configFactory = $config_factory;
$this->logger = $logger;
$this->dateFormatter = $date_formatter;
$this->messengerService = $messenger;
$this->usersSession = $session;
$this->userData = $user_data;
$this->currentUser = $account;
try {
$this->nodeStorage = $this->entityTypeManager->getStorage('node');
$this->termStorage = $this->entityTypeManager->getStorage('taxonomy_term');
$this->fileStorage = $this->entityTypeManager->getStorage('file');
$this->imageStyleStorage = $this->entityTypeManager->getStorage('image_style');
} catch (PluginNotFoundException $exception) {
throw new NotFoundHttpException("Plugin Not Found Exception");
} catch (InvalidPluginDefinitionException $exception) {
throw new NotFoundHttpException("Invalid Plugin Definition Exception");
}
}
I also define here the Node, Term, File, Image Style Storage Managers which are used on the rest of the class.
Inside this Service class I wrote reusable functions to be used from other Modules. For example this is the code to load the Preview Term data to show inside my Bootstrap 4 column element teaser's custom block:
/**
* Get the Taxonomy Details by Taxonomy ID.
* @param int $tid
*
* @return array $term_item
* An associative array containing tid/taxonomy details value pairs.
*/
public function getServiceDetails($tid)
{
$term_item = [];
$term = $this->termStorage->load($tid);
if ($term instanceof ContentEntityInterface) {
$image_data = $this->getImageData($term, 'field_service_image');
$term_id = $term->id();
$term_url = $term->toUrl()->toString();
if ($term->hasField('field_term_teaser_title')) {
$caption = $term->field_term_teaser_title->value;
}
if (empty($caption)) {
$title_parts = explode(' ', $image_data['title']);
$caption = $title_parts[0];
}
if ($term->hasField('field_term_teaser')) {
$description = strip_tags($term->field_term_teaser->value);
}
if (empty($description)) {
$description = $this->getFirstWords(strip_tags($term->description->value), 137);
}
$term_item = array(
"tid" => $term_id,
'key' => 1,
"name" => $term->label(),
"image" => $image_data['url'],
'caption' => $caption,
'alt' => $image_data['alt'],
'title' => $image_data['title'],
'href' => $term_url,
'description' => $description,
);
}
return $term_item;
}
In my Tags Vocabulary I created 2 custom fields (field_term_teaser and field_term_teaser_title) to store the information to Display next to the image and I have the field_service_image to store the image.
Remember to make the code as generic as possible so it can works for all ContentEntityInterface classes. For example this is the code to load the Preview Node data that it is shown inside inside my Bootstrap 4 column Node's Preview custom block:
public function getSpecialService($nid)
{
$service_item = [];
$node = $this->nodeStorage->load($nid);
if ($node instanceof ContentEntityInterface) {
$image_data = $this->getImageData($node, 'field_image');
$nid = $node->id();
$node_url = $node->toUrl()->toString();
// Caption is the first word in the image title.
$title_parts = explode(' ', $image_data['title']);
$caption = $title_parts[0];
if ($node->hasField('field_image_text_preview')) {
$description = $node->field_image_text_preview->value;
}
if (empty($description) && $node->hasField('field_teaser')) {
$description = $this->getFirstWords(strip_tags($node->field_teaser->value), 137);
}
$service_item = array(
"nid" => $nid,
'key' => 1,
"name" => $node->label(),
"image" => $image_data['url'],
'caption' => $caption,
'alt' => $image_data['alt'],
'title' => $image_data['title'],
'href' => $node_url,
'description' => $description,
);
}
return $service_item;
}
In my Service Content Type I created 2 custom fields (field_term_teaser and field_image_text_preview) to store the information to Display next to the image and I have the field_image to store the image.
Both Node and Term classes implement the ContentEntityInterface Interface which means that you can create a function to load the image from both Entity from both bundles, you only need to know the name of the image custom field in my case is:
public function getImageData(ContentEntityInterface $contentEntity, $field_name)
{
$image_data = [
'url' => '/themes/maria_consulting/img/image-blank.png',
'alt' => '',
'title' => '',
];
$field_image = FALSE;
if ($contentEntity->hasField($field_name)) {
/** @var \Drupal\field\Entity\FieldConfig $def */
$def = $contentEntity->getFieldDefinition($field_name);
if ($def->getType() == 'image') {
/** @var \Drupal\Core\Field\FieldItemListInterface $field_image */
$field_image = $contentEntity->get($field_name);
}
}
if ($field_image && !$field_image->isEmpty()) {
$values = $field_image->getValue();
if (!empty($values[0])) {
$value = $values[0];
$image_data['alt'] = $value['alt'];
$image_data['title'] = $value['title'];
/** @var File $file */
$file = $this->fileStorage->load($value['target_id']);
$image_uri = $file->getFileUri();
/** @var ImageStyle $image_style */
$image_style = $this->imageStyleStorage->load('medium');
$image_data['url'] = $image_style->buildUrl($image_uri);
}
}
if (empty($image_data['title'])) {
$image_data['title'] = $contentEntity->label();
}
return $image_data;
}
This function will work on any Term and Node as long as the Entity has got a Custom Field to store the image. It returns an array with the values of the image such as width, height, alt, title, URI plus the full path of the image style medium.