3

I have been diving into Drupal 8, it's plugin system and all the new stuff for some days now. I hope that I understood the basics and now I would like to start out with a new module.

The module should:

  • Add a dropdown to the form menu_link_edit
  • do the usual save/update/delete stuff for each menu link on submission of the form, so that the selected value can be accessed from the link entity.

Following the conventions I have created that structure:

+mymodule
 -mymodule.info.yml
 -mymodule.services.yml
 +src
  -MyModuleManager.php extends DefaultPluginManager
  -MyTypeInterface.php
  -MyTypeBase.php implements MyTypeInterface
  +Plugin
   +Mytypes
    -MyType1.php extends MyType
    -MyType2.php extends MyType

Until here the new whole Plugin stuff is clear to me, that is why I have not posted any code so far. With the above I declare the Items that should appear in the desired dropdown, but it does not attach the field to the form, and it does not care about the storage of the values.

I had a look at the module menu_link_attributes and there, in menu_link_attributes.module I found:

function menu_link_attributes_form_menu_link_content_form_alter(…){
    //code
    $form['actions']['submit']['#submit'][] = 'menu_link_attributes_menu_link_content_form_submit';
}

So the actual form is modified and another submit handler is added. BUT I do not get the part where the data (the values of the additional fields) is stored with the item (the menu link). HOW DOES THAT WORK?

On the other Hand, I had a look at the module link_attributes, therein (link_attributes.module) I found:

function link_attributes_entity_base_field_info_alter(&$fields, EntityTypeInterface $entity_type) {
    if ($entity_type->id() === 'menu_link_content') {
        $fields['link']->setDisplayOptions('form', [
            'type' => 'link_attributes',
            'weight' => -2,
        ]);
    }
}

And if I understood everything right, this piece of code does assign a different Field Widget to the Link, so that additional fields extend the original widget.

I think I understand the process, but also here, I do not understand how the data is stored/retrieve.

Since the overall question might be unclear, I hope I can figure out my objective like that:

From my understanding I would expect to alter the entity menu_link_content in some sort of hook_entity_alter, or if possible in the new D8 manner. But in non of the examples I could find any approach like that, why?

So by defining a new field (which renders the dropdown) and adding that field to the definition of menu_link_content the field should appear on the form all by itself, or do I get this totally wrong?

Thanks in advance!

UPDATE

I have implemented the following, but it does not do anything, so why is that not working?

function mymodule_entity_type_build(array &$entity_types) {
    $entity_types['menu_link_content']
        ->setFormClass('menu_link_content_form',
            'Drupal\mymodule\Form\MenuLinkContentForm');
}

I have intentionally implemented errors in 'Drupal\mymodule\Form\MenuLinkContentForm', just to check if it is used, but it isn't. So where's the catch here?

UPDATE #2

based on the accepted answer My resulting code looks like that:

//mymodule.module

use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;


function mymodule_entity_base_field_info(EntityTypeInterface $entityType)
{
    if($entityType->id() == 'menu_link_content') {

        $manager = \Drupal::service('mymodule.pluginmanager');

        $labels = array_map(
            function(array $definition) use ($manager)
            {
                $instance = $manager->createInstance($definition['id']);
                return $instance->getName();
            },
            $manager->getDefinitions());

        $field = [
            'mymodulefield' => BaseFieldDefinition::create('list_string')
                ->setLabel(t('MYMODULE::FIELD::LABEL'))
                ->setSetting('allowed_values', $labels)
                ->setDisplayConfigurable('form', true)
                ->setDisplayConfigurable('view', true)
                ->setDisplayOptions('form', [
                    'type' => 'select',
                    'weight' => -10
                ])
        ];

        return $field;
    }
}

One thing made a lot of trouble: If during the installation of the module something went wrong, It resulted in MySql errors. Uninstalling was not possible anymore, since extra fields were attached to the entity.

2
  • If you want to get allowed values from another source, it's better to use allowed_values_function and define a function which fetches the allowed values. Otherwise you can get into trouble and can get cache issues
    – googletorp
    Oct 18, 2016 at 12:26
  • I also had MySQL errors that the column of the new field does not exist in the menu_link_content_data table. You can use hook_update_n to update the schema and add the missing column.
    – consuela
    Jan 23, 2018 at 12:48

1 Answer 1

8

The easy solution for this, is the add a new base field on the menu link entity, the Drupal the rest for you. You can do this with the hook_base_field_info() hook.

This code should work, but you might need to tweak it.

/**
 * Implements hook_entity_base_field_info().
 */
function MODULE_entity_base_field_info(\Drupal\Core\Entity\EntityTypeInterface $entity_type) {
  if ($entity_type->id() === 'menu_link_content') {
    $fields['custom_field'] = BaseFieldDefinition::create('list_string')
      ->setLabel(t('LABEL'))
      ->setSetting('allowed_values_function', 'function_to_control_options')
      ->setDisplayOptions('form', array(
        // Default form display options
      ))
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE);

    return $fields;
  }
}

The code above creates a list_string field with an allowed values function and makes it configurable for both form and view mode.

The nice thing with the above approach is that the field gets added to the menu link field_data table, so Drupal don't need to do extra joins etc. For form/display it uses fields like all of the other stuff menu links are built with, so you don't need to touch the Entity class, the form etc.

To get/set the field, you need to use the ::get / ::set method directly or access the field directly. Both is not considered best practice, but would work. To make the beautiful solution, you could create a custom entity class with getter and setter for the new base field.

5
  • Thanks a lot! Gonna have a look at it! Do you know where can I find documentation for that setSetting() method? Will the available options be assigned once, or can they be dynamic?
    – philipp
    Oct 17, 2016 at 13:34
  • @philipp setSetting / setSettings is locked. It's what happens internally when you configure a config field. I'm not sure there is documentation for it, I found out looking at code and yml exports.
    – googletorp
    Oct 17, 2016 at 13:57
  • So I tried your code: 1. It does run :), 2. nothing happens, so I guess I am missing something fundamental. And I guess I is connected to the setDisplayOptions(…) line, since I should add a form there ??? Even if I found the definition of this setSetting() in the meantime, I do not understand what it actually does… Would be really nice If you could provide some more details :)
    – philipp
    Oct 17, 2016 at 18:42
  • @philipp If you reinstall the module fields should be added automatically, otherwise you need to make an update hook. setSetting(s) is used to set handler settings.
    – googletorp
    Oct 17, 2016 at 19:13
  • Your answer led me on the right way, so thanks for that!
    – philipp
    Oct 18, 2016 at 10:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.