Feeds

Author

Jul 14 2014
Jul 14

In this article we will look at how to define a configuration entity type that will serve a simple purpose, describe dummy flower configuration entities. We will do so in a module called flower and will use the alpha13 release of Drupal 8 to do it.

Before we get started, let's define a practical goal for this tutorial. As I said, we will have a flower config entity type with a couple of properties: name, number of petals, color and season. And by the end, we will have a fully fledged UI to create and manage them. The final code you can also find in this repository.

So let's begin.

The configuration entity interface

The first thing we need to do is define an interface our Flower entity type class can implement and that extends the default ConfigEntityInterface. So inside of our module's src/ folder, create a file called FlowerInterface.php with the following interface:

/**
 * @file
 * Contains \Drupal\flower\FlowerInterface.
 */
 
namespace Drupal\flower;
 
use Drupal\Core\Config\Entity\ConfigEntityInterface;
 
/**
 * Provides an interface defining a flower entity type.
 */
interface FlowerInterface extends ConfigEntityInterface {
 
}

As you can see, we are just extending the default configuration entity interface without adding any methods to it (which is possible).

The configuration entity class

Next, we will focus on the crux of defining our own configuration entity class. Go ahead and create a folder inside the src/ directory called Entity, and within it, a file called FlowerEntity.php:

/**
 * @file
 * Contains \Drupal\flower\Entity\FlowerEntity.
 */
 
namespace Drupal\flower\Entity;
 
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Config\Entity\ConfigEntityInterface;
use Drupal\flower\FlowerInterface;
 
    /**
     * Defines a Flower configuration entity class.
     *
     * @ConfigEntityType(
     *   id = "flower",
     *   label = @Translation("Flower"),
     *   fieldable = FALSE,
     *   controllers = {
     *     "list_builder" = "Drupal\flower\FlowerListBuilder",
     *     "form" = {
     *       "add" = "Drupal\flower\Form\FlowerForm",
     *       "edit" = "Drupal\flower\Form\FlowerForm",
     *       "delete" = "Drupal\flower\Form\FlowerDeleteForm"
     *     }
     *   },
     *   config_prefix = "flower",
     *   admin_permission = "administer site configuration",
     *   entity_keys = {
     *     "id" = "id",
     *     "label" = "name"
     *   },
     *   links = {
     *     "edit-form" = "flower.edit",
     *     "delete-form" = "flower.delete"
     *   }
     * )
     */
    class FlowerEntity extends ConfigEntityBase implements FlowerInterface {
 
      /**
       * The ID of the flower.
       *
       * @var string
       */
      public $id;
 
      /**
       * The flower name.
       *
       * @var string
       */
      public $name;
 
      /**
       * The flower color.
       *
       * @var string
       */
      public $color;
 
      /**
       * The number of petals.
       *
       * @var int
       */
      public $petals;
 
      /**
       * The season in which this flower can be found.
       *
       * @var string
       */
      public $season;
 
    }

What we have here is a simple class defining the entity properties we want (name, id, color, number of petals and season). This class extends the default ConfigEntityBase class and implements our interface. What happens above the class definition is what's interesting though.

Using annotations, we are basically telling Drupal about our Flower entity type.

The @ConfigEntityType tells Drupal that this is a configuration entity type (as opposed to a plugin or something else). Within its definition, we have an array-like structure with the following information (I will only mention the keys that are not super obvious):

  • label - the label of the entity type passed through the translation system.
  • fieldable - the configuration entities are not fieldable, but the content entities are. Since we are using the same entity API, we can specify this.
  • controllers - all the classes needed to manage these entities. The list_builder class will provide an admin overview interface of the entities, whereas the form classes are used to perform the CRUD operations through the UI.
  • config_prefix - a configuration identifier
  • entity keys - mapping of the main entity keys to the entity properties we defined. For instance, when we call the label() method on the entity object, it will return the flower name.
  • links - administration links for editing and deleting entities with values referencing routes. Specifying them here will make Drupal add them automatically to the operations column on the entity overview page (we'll see this in a minute).

For more information about the structure of an entity class annotation, follow this documentation page.

The entity forms

The next thing we need to do is create the forms we referenced in the annotations above: for adding, editing and deleting flower entities. The cool thing is that the form for adding can be reused for editing as well. For delete, we extend a special class that gives us all we need for a confirmation form. But first, the add/edit form (FlowerForm.php) inside of the src/Form/ folder:

/**
 * @file
 * Contains \Drupal\flower\Form\FlowerForm.
 */
 
namespace Drupal\flower\Form;
 
use Drupal\Core\Entity\EntityForm;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Url;
 
/**
 * Class FlowerForm
 *
 * Form class for adding/editing flower config entities.
 */
class FlowerForm extends EntityForm {
 
   /**
   * {@inheritdoc}
   */
  public function form(array $form, array &$form_state) {
 
    $form = parent::form($form, $form_state);
 
    $flower = $this->entity;
 
    // Change page title for the edit operation
    if ($this->operation == 'edit') {
      $form['#title'] = $this->t('Edit flower: @name', array('@name' => $flower->name));
    }
 
    // The flower name.
    $form['name'] = array(
      '#type' => 'textfield',
      '#title' => $this->t('Name'),
      '#maxlength' => 255,
      '#default_value' => $flower->name,
      '#description' => $this->t("Flower name."),
      '#required' => TRUE,
    );
 
    // The unique machine name of the flower.
    $form['id'] = array(
      '#type' => 'machine_name',
      '#maxlength' => EntityTypeInterface::BUNDLE_MAX_LENGTH,
      '#default_value' => $flower->id,
      '#disabled' => !$flower->isNew(),
      '#machine_name' => array(
        'source' => array('name'),
        'exists' => 'flower_load'
      ),
    );
 
    // The flower color.
    $form['color'] = array(
      '#type' => 'textfield',
      '#title' => $this->t('Color'),
      '#maxlength' => 255,
      '#default_value' => $flower->color,
      '#description' => $this->t("Flower color."),
      '#required' => TRUE,
    );
 
    // The number of petals.
    $form['petals'] = array(
      '#type' => 'textfield',
      '#title' => $this->t('Petals'),
      '#maxlength' => 255,
      '#default_value' => $flower->petals,
      '#description' => $this->t("The number of petals."),
      '#required' => TRUE,
    );
 
    // The season.
    $form['season'] = array(
      '#type' => 'select',
      '#options' => array(
        'Spring' => 'Spring',
        'Summer' => 'Summer',
        'Automn' => 'Automn',
        'Witer' => 'Winter'
      ),
      '#title' => $this->t('Season'),
      '#maxlength' => 255,
      '#default_value' => $flower->season,
      '#description' => $this->t("The season in which this flower grows."),
      '#required' => TRUE,
    );
 
    return $form;
  }
 
  /**
   * {@inheritdoc}
   */
  public function save(array $form, array &$form_state) {
 
    $flower = $this->entity;
 
    $status = $flower->save();
 
    if ($status) {
      // Setting the success message.
      drupal_set_message($this->t('Saved the flower: @name.', array(
        '@name' => $flower->name,
      )));
    }
    else {
      drupal_set_message($this->t('The @name flower was not saved.', array(
        '@name' => $flower->name,
      )));
    }
    $url = new Url('flower.list');
    $form_state['redirect'] = $url->toString();
 
  }
 
} 

In our FlowerForm class we are extending the Drupal EntityForm class and implementing 2 of its methods: form() and save(). In the first one, we define a regular Form API form very similar to what we do in Drupal 7. But there are a few cool new things happening there as well:

  • We extend the parent form and add our elements to that definition.
  • We get the configuration entity object from the entity property of the parent class.
  • We check the operation being performed on the entity and if the user is editing it, we change the title of the page to reflect this
  • Instead of using the procedural t() function, we access $this->t() on the parent class for best practice.
  • We access the config entity public properties and set them as the defaults in the form elements' definition.
  • For the machine_name, we use the flower_load() helper function (that we will need to define in our .module file) in order to automatically check whether an entity with that ID already exists.

In the save() method we perform the simple operation of saving the entity object to the configuration system. Couldn't get simpler than this. And after the save is performed, we redirect to the flower entity overview page. Here we use the Url class to build a url object based on a route (that we will define later).

Next, let's quickly create the delete form.

Inside the same src/Form/ folder, create a FlowerDeleteForm.php file with the following class:

/**
 * @file
 * Contains \Drupal\flower\Form\FlowerDeleteForm.
 */
namespace Drupal\flower\Form;
 
use Drupal\Core\Entity\EntityConfirmFormBase;
use Drupal\Core\Url;
 
/**
 * Form that handles the removal of flower entities.
 */
class FlowerDeleteForm extends EntityConfirmFormBase {
 
  /**
   * {@inheritdoc}
   */
  public function getQuestion() {
    return $this->t('Are you sure you want to delete this flower: @name?', array('@name' => $this->entity->name));
  }
  /**
   * {@inheritdoc}
   */
  public function getCancelRoute() {
    return new Url('flower.list');
  }
  /**
   * {@inheritdoc}
   */
  public function getConfirmText() {
    return $this->t('Delete');
  }
  /**
   * {@inheritdoc}
   */
  public function submit(array $form, array &$form_state) {
 
    // Delete and set message
    $this->entity->delete();
    drupal_set_message($this->t('The flower @label has been deleted.', array('@label' => $this->entity->name)));
    $form_state['redirect_route'] = $this->getCancelRoute();
 
  }
}

With this form class we are extending the Drupal EntityConfirmFormBase that provides us with all we need for a delete confirmation form. By implementing these self-explanatory methods, we take care of the entity delete process. Finally, it's time to define the admin overview page.

The entity list builder

As we declared when defining the config entity class, we now need a class file responsible for building the overview page of our entities. So straight in the src/ folder of our module you can create a FlowerListBuilder.php class file with the following class:

/**
 * @file
 *
 * Contains Drupal\flower\FlowerListBuilder
 */
 
namespace Drupal\flower;
 
use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
use Drupal\Core\Entity\EntityInterface;
 
 
class FlowerListBuilder extends ConfigEntityListBuilder {
 
  /**
   * {@inheritdoc}
   */
  public function buildHeader() {
    $header['label'] = $this->t('Name');
    $header['color'] = $this->t('Color');
    $header['petals'] = $this->t('Number of petals');
    $header['season'] = $this->t('Season');
    return $header + parent::buildHeader();
  }
 
  /**
   * {@inheritdoc}
   */
  public function buildRow(EntityInterface $entity) {
 
    // Label
    $row['label'] = $this->getLabel($entity);
 
    // Color
    $row['color'] = $entity->color;
 
    // Petals
    $row['petals'] = $entity->petals;
 
    // Season
    $row['season'] = $entity->season;
 
    return $row + parent::buildRow($entity);
  }
 
  /**
   * {@inheritdoc}
   */
  public function render() {
 
    $build = parent::render();
 
    $build['#empty'] = $this->t('There are no flowers available.');
    return $build;
  }
 
} 

In this class that extends the ConfigEntityListBuilder, we implement three methods. The buildHeader() method is responsible for creating the table header of our overview page whereas buildRow() will create the rows based on the number of entities and their values. Lastly, we are overriding the render() method so that we can specify a custom message to display in case there are no entities to show (personal preference). And that's basically it with the list builder class.

Miscellaneous

There are a few more things we need to take care of in order to round up our configuration entity type. The first one has just became kind of mandatory so I'll start with that: the configuration schema. So let's quickly create the folder structure inside our module (config/schema/) and inside a file called flower.schema.yml we can have the following:

# Schema for the configuration files of the Flower module.

    flower.flower.*:
      type: mapping
      label: 'Flower'
      mapping:
        id:
          type: string
          label: 'Flower identifier'
        uuid:
          type: string
          label: 'UUID'
        name:
          type: label
          label: 'Name'
        color:
          type: string
          label: 'Color'
          translatable: true
        petals:
          type: integer
          label: 'Number of petals'
        season:
          type: string
          label: 'Season'
          translatable: true

On the first line (after the comment) we start defining the schema for the (flower module).(flower configuration entity type).(all flower configuration entities). And it follows to map all the entity properties and specify what data type they are. Although the uuid property was not defined by us, Drupal adds it by default and we can specify it here.

As far as I could tell, the label-typed properties become translatable automatically whereas for all the rest we want translatable we can specify translatable: true. Translation is one of the biggest reasons for which we use these schemas for configuration entities.

And now that the schema is taken care of, it's time for some finishing touches. First, let's create our routes so that we can access everything in the browser. Inside of a file called flower.routing.yml in the module root folder, add the following:

flower.list:
  path: '/admin/structure/flowers'
  defaults:
    _entity_list: 'flower'
    _title: 'Flowers'
  requirements:
    _permission: 'administer site configuration'
flower.add:
  path: '/admin/structure/flowers/add'
  defaults:
    _entity_form: 'flower.add'
    _title: 'Add a new flower'
  requirements:
    _permission: 'administer site configuration'
flower.edit:
  path: '/admin/structure/flowers/edit/{flower}'
  defaults:
    _entity_form: 'flower.edit'
    _title: 'Edit flower'
  requirements:
    _permission: 'administer site configuration'
flower.delete:
  path: '/admin/structure/flowers/delete/{flower}'
  defaults:
    _entity_form: 'flower.delete'
    _title: 'Delete flower'
  requirements:
    _permission: 'administer site configuration'

For more information about the structure of a route file (and what the above keys actually mean), please consult this documentation page. But an important take-away are the paths we defined at admin/structure/flowers.

Second, on the flower overview page, we'd probably like a link to add new flowers to the site. So let's create another YML file in the root of our module called flower.local_actions.yml to define that link:

flower.add:
  route_name: 'flower.add'
  title: 'Add flower'
  appears_on:
    - flower.list

This is a simple local action link definition called flower.add that uses the flower.add route and appears on the page given by the route flower.list. For more information about defining local actions, consult this documentation page.

Third, we can create a menu link under the Structure admin menu that will take us to the flower overview page. So inside of a file called flower.menu_links.yml in the module root folder, add the following:

flower.list:
  title: Flowers
  description: 'Administer the flower entities'
  parent: system.admin_structure
  route_name: flower.list

Here we create a link called flower.list found under the system.admin_structure link and that uses the flower.list route name. Simple.

Finally, we need to create the auto loader function that will be used by the machine_name form element to check whether an entity with a given machine name already exists (on the flower add form). So inside the flower.module file, create this function:

/**
 * Menu argument loader. Returns a flower entity
 *
 * @param $id
 * @return \Drupal\Core\Entity\EntityInterface|static
 */
function flower_load($id) {
  return FlowerEntity::load($id);
}

And don't forget to use the FlowerEntity class at the top of the file:

use \Drupal\flower\Entity\FlowerEntity;

And that should be about it. Clear the caches, make sure the module is enabled, and start poking at it. Navigate to /admin/structure/flowers and create, edit, delete flower entities. Additionally, you can turn on configuration translation and translate all your entities into multiple languages. Cool, no?

Conclusion

In this tutorial we've looked at how we can create our own simple configuration entity type in Drupal 8. The alpha13 version (latest at the time of writing) has been used for this, so make sure that if you are using a newer one you make the necessary code adaptations if needed.

In Drupal 7 we do not have configuration entities and we are left with creating custom tables that hold data meant as configuration. And obviously, integration with the modest D7 entity API is practically inexistent. This all changes in Drupal 8 with the development of a robust entity API - fully integrated with the multilingual and configuration systems. Because of this, we now have exportable and translatable configuration entities used to manage more complex data that is not content.

And with all these new developments, we are being introduced to a few new concepts that can scare us a bit (services, dependency injection, plugins, OOP and so on). However, once we get used to them a bit, they will become a friend rather than foe and open the door to more sane, performant and modern development within the Drupal framework.

Jan 20 2014
Jan 20

To illustrate this power, we’ll write some code, but first, let’s see some theory.

What are the states and what do they do?

The states are simple properties or HTML attributes of DOM elements. When creating your form elements using the Form API, you can apply various states to them depending on the states of others. Thus there are 2 kinds of elements: those which act as the condition for others and those which depend on these conditions to change their state. I like the way Jeff Robbins of Lullabot categorises the elements by the possible states they can have: callers and listeners.

And what does Drupal do? It provides all the necessary javascript to make all these behavioral alterations on the fly so you don’t have to worry about javascript or jQuery.

Example module

To illustrate how this all works, we are going to build a tiny module which declares a path to a form. Using this form, we will play around with some elements and see all this in action. So let’s dive in. You can download this small module if you want to follow along.

Assuming you have done the necessary to create your empty module, we can go ahead and declare the menu path using hook_menu():

/**
 * Implements hook_menu().
 */
 
function states_menu() {
 
  $items['states-example'] = array(
    'title' => 'Demonstrating the Form API states system',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('states_example_form'),
    'type' => MENU_NORMAL_ITEM,
    'access arguments' => array('access content'),
  );
 
  return $items;
}

This registers the states-example path which will render the states_example_form declared below:

function states_example_form($form, &$form_state) {
 
  $form = array();
 
  $form['name'] = array(
    '#title' => t('Your name'),
    '#type' => 'textfield',
  ); 
 
  $form['submit'] = array(
    '#type' => 'submit', 
    '#value' => t('Submit'),
  );
 
  return $form;
}

If you clear the cache and go to that path, you should see the form with 2 form elements: the name field and the submit button. No biggie. One thing to keep in mind for later is that the array key 'name' will be the actual DOM name of the form element.

Lets now add another element below the name field that will depend on the latter and show up only if the user enters something in the name field:

  $form['preferences'] = array(
    '#title' => t('Your preference'),
    '#type' => 'select',
    '#options' => array('moto' => 'Motorcycles', 'cars' => 'Cars'),
     // Show this field only if the name field is filled.
    '#states' => array(
      'visible' => array(
        'input[name="name"]' => array('filled' => TRUE),
      ),
    ),
  );

Lets scan through this array and see what’s what. Above the comment we can see that the element is a select list with 2 options and a title. Below the comment we declare that to this element will be applied the state visible when and if the DOM element of the type input with the name="name" (your classic css/jQuery selector and which happens to be the first element in our form) gets filled. Otherwise, the opposite state is applied - in this case, invisible.

So in the browser when the form is loaded on the page, only the name field is visible. When the user starts typing the name, the preferences field appears. Refresh the page and check it out. Drupal adds the necessary javascript for this to happen.

You can also do it the other way around. You can apply the state invisible when another element => array('filled' => FALSE). It will have the same effect except you declare it differently.

Next, let’s add another element that will depend on the preferences field:

  $form['brands'] = array(
    '#title' => t('Your favorite brand'),
    '#type' => 'select',
    '#options' => array('Toyota', 'BMW', 'Audi'),
     // The reverse of the previous field: hide this field if there is no name
    '#states' => array(
      'visible' => array(
        'select[name="preferences"]' => array('value' => 'cars'),
      ),
    ),
  );

This is another select list but the visibility of this element depends on the actual value of the previous one. So in the browser, this will only appear if and when the user selects that they prefer cars over motorcycles.

Let’s look at the next 2 elements:

  $form['marriage'] = array(
    '#title' => t('Are you married?'),
    '#type' => 'checkbox',
  );
 
  $form['spouse_preferences'] = array(
    '#title' => t('Your spouse\'s preference'),
    '#type' => 'radios',
    '#options' => array('moto' => 'Motorcycles', 'cars' => 'Cars'),
     // Show this field only if the user name is filled and if s/he is married.
    '#states' => array(
      'visible' => array(
        'input[name="name"]' => array('filled' => TRUE),
        'input[name="marriage"]' => array('checked' => TRUE),
      ),
    ),
  );

The first one is a simple checkbox that will show no matter what. But the second one is another select list and here we can see that the visibility of this depends on 2 previous elements. So this will show only when and if the user fills in a name and checks the box confirming that s/he is married. A so called AND condition, if you will. I will tell you write off the bat that you cannot do OR conditions with the states system - at least not that I could figure out. If you manage to find a way, let me know.

And the last two elements we will look at in this article:

  $form['kids'] = array(
    '#title' => t('How many kids do you have?'),
    '#type' => 'select',
    '#options' => array(0, 1, 2),
  );
 
  $form['kids_preferences'] = array(
    '#title' => t('Your kids\' preference'),
    '#type' => 'radios',
    '#options' => array('moto' => 'Motorcycles', 'cars' => 'Cars'),
     // Show this field only if the user has kids.
    '#states' => array(
      'visible' => array(
        'input[name="marriage"]' => array('checked' => TRUE),
      ),
      'disabled' => array(
        'select[name="kids"]' => array('value' => '0'),
      ),
    ),
  );

Again the first one will show no matter what, but this time it's a select list. The second one depends on two other elements for two different state changes. First, it will become visible only if the the user checks the box that s/he is married. Second, it will become disabled if the user selects that s/he has 0 kids. Neat.

Conclusion

As i mentioned in the beginning of the article, there are a number of different states available for elements and Drupal handles all the javascript for you. We cannot go over all of them here nor try all the different combinations of what you can do with the states system. I thus encourage you experiment further on your own. You can even download this little module and use it as a starting point.

And as always, Drupal.org is your friend and you can find some more information on the states system at the following pages:

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web