5

There are already some resources describing how to write a custom content entity for Drupal 8. Perhaps the most useful one is the one included in the Examples module.

However I'd like a super-basic example that introduces Drupal 8 and PHP5 OOP concepts step-by-step, without throwing me in the deep end.

9

Building a basic content entity type in Drupal 8.

In this case we create a Drupal 8 content entity to match a node from our Drupal 7 site, in preparation for migrating this data.

The entity does not implement the Field API so remains in code throughout. Nevertheless it can be a useful skeleton for building out Content Entities.

Background.

This stems from the need to build a basic custom drupal 8 content entity to hold data coming in from a migration. Elsewhere we will explore writing tests to drive our field code and plugins for field constraints For the time being it is suffice to say:

Our new Drupal 8 advertiser content entity will have basetable fields:

  • UUID
  • ID
  • Advertiser Name

Defining a Content Entity in Drupal 8.

First all our custom entities now live at web/modules/custom. Inside our custom entity the file structure that we will end up with looks like this:

web/modules/custom/advertiser$
├── advertiser.info.yml
└── src
    ├── Entity
    │   └── Advertiser.php

We start by defining our custom module in module_name.info.yml. This is self explanatory:

name: Advertiser
type: module
description: 'Barebones advertiser entity'
package: custom
core: 8.x

Meanwhile The basic Advertiser entity class and associated schema is defined in src/Entity/Advertiser.php.

We start with an annotation that describes the class that our file contains.

/**
 * @file
 * Contains \Drupal\advertiser\Entity\Advertiser.
 */

The next thing we do is define a namespace for our Advertiser Entity Class. This will come in handy whenever we want to use our classes. namespace Drupal\advertiser\Entity;

Now it is time to define our entity, which we do in an annotation! - TIP: This is the actual definition of the entity type it is read and cached so be sure to clear the cache after any changes.

/**
 * Defines the Advertiser entity.
 *
 * @ingroup advertiser
 *
 * @ContentEntityType(
 *   id = "advertiser",
 *   label = @Translation("Advertiser"),
 *   base_table = "advertiser",
 *   entity_keys = {
 *     "id" = "id",
 *     "uuid" = "uuid",
 *   },
 * )
 */

Because this is a barebones entity we only use a few properties and no handlers (like access). For a comprehensive list of available options for your entity see the docs here: https://www.drupal.org/node/2192175

At this point we have a functional module that defines our custom content entity, however if we go ahead and enable the module we will see that the 'advertiser' table has not been created in the database.

drush sqlc
SHOW TABLES;

This is because our class doesn't have any methods that explicitly interact with the database. Furthermore we need a description of the bare minimum of methods needed for an entity to interface satisfactorily with the Database.

Generally we can add classes by adding something like use Drupal\Core\Entity\ContentEntityBase; after our namespace defintiion at the top of our script. This makes these methods available to our own class, which can extend them or in the case of Interfaces, implement them.

We do two things, we extend an existing ContentEntityBase class that already has the necessary methods to interact with the DB, and implement an ContentEntityInterface to describe...

the methods that we need to access our database. It does NOT describe in any way HOW we achieve that. That's what the IMPLEMENTing class does. We can IMPLEMENT this interface as many times as we need in as many different ways as we need. We can then switch between implementations of the interface without impact to our code because the interface defines how we will use it regardless of how it actually works. - https://secure.php.net/manual/en/language.oop5.interfaces.php

All this means is that we end up with the following: Tip Remember to add any new classes through a use statement at the top of our script:

class Advertiser extends ContentEntityBase implements ContentEntityInterface {

But we still need use these new useful methods to put something in the database, we start with the basic fields for our entity.

The method baseFieldDefinitions comes from ContentEntityBase class that we are extending.
It takes two parameters:

The entity type definition. Useful when a single class is used for multiple, possibly dynamic entity types.

And it returns

An array of base field definitions for the entity type, keyed by field name.

So we implement it like this:

public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {

  // Standard field, used as unique if primary index.
  $fields['id'] = BaseFieldDefinition::create('integer')
    ->setLabel(t('ID'))
    ->setDescription(t('The ID of the Contact entity.'))
    ->setReadOnly(TRUE);

  // Standard field, unique outside of the scope of the current project.
  $fields['uuid'] = BaseFieldDefinition::create('uuid')
    ->setLabel(t('UUID'))
    ->setDescription(t('The UUID of the Contact entity.'))
    ->setReadOnly(TRUE);

  return $fields;
}

So altogether we have this:

<?php
/**
 * @file
 * Contains \Drupal\advertiser\Entity\Advertiser.
*/

namespace Drupal\advertiser\Entity;

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

/**
 * Defines the advertiser entity.
 *
 * @ingroup advertiser
 *
 * @ContentEntityType(
 *   id = "advertiser",
 *   label = @Translation("advertiser"),
 *   base_table = "advertiser",
 *   entity_keys = {
 *     "id" = "id",
 *     "uuid" = "uuid",
 *   },
 * )
 */

class Advertiser extends ContentEntityBase implements ContentEntityInterface {

  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {

  // Standard field, used as unique if primary index.
    $fields['id'] = BaseFieldDefinition::create('integer')
      ->setLabel(t('ID'))
      ->setDescription(t('The ID of the Contact entity.'))
      ->setReadOnly(TRUE);
 // Standard field, unique outside of the scope of the current project.
    $fields['uuid'] = BaseFieldDefinition::create('uuid')
      ->setLabel(t('UUID'))
      ->setDescription(t('The UUID of the Contact entity.'))
      ->setReadOnly(TRUE);
  return $fields;
  }
}

Now when we run drush en advertiser -y; drush sqlc we can see the advertiser basetable has been added to the database!

  • That's one way to write tutorials I guess, but note that Drupal Answers isn't meant for answers that would fill a whole blog post. Would be awesome if you could help me bring the entity documentation (drupal.org/developing/api/entity) up to speed. It has similar pages already, would be nice to have those updated and verified. – Berdir Mar 3 '16 at 19:59
  • @Bedir, would love too. Where can I put this? – iain maitland Mar 3 '16 at 20:07
  • See drupal.org/node/2629550 for example, you could improve/check that. documentation pages on drupal org can be edited by anyone. – Berdir Mar 3 '16 at 20:24
  • 1
    Just wanted to comment that learning it this way is highly highly advised instead of using Drupal console outright. REALLY helps to understand the code before using convenient tools. – Kevin Mar 4 '16 at 0:35
  • @Berdir - I added this at: drupal.org/node/2683569 not sure how adding it to the relevant page/sub-page works ... perhaps you could do that. I have similar tutorials for using Tests to drive further development of the content entity, adding custom field constraints/validators and finally migrating a D7 node into the new D8 custom content entity... Will post when done. – iain maitland Mar 8 '16 at 17:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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