How to create a Custom Drupal 8 Content Entitiy? - Baby steps edition 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.
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')
    -&gt;setLabel(t('ID'))
    -&gt;setDescription(t('The ID of the Contact entity.'))
    -&gt;setReadOnly(TRUE);

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

  return $fields;
}


So altogether we have this:  

&lt;?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')
      -&gt;setLabel(t('ID'))
      -&gt;setDescription(t('The ID of the Contact entity.'))
      -&gt;setReadOnly(TRUE);
 // Standard field, unique outside of the scope of the current project.
    $fields['uuid'] = BaseFieldDefinition::create('uuid')
      -&gt;setLabel(t('UUID'))
      -&gt;setDescription(t('The UUID of the Contact entity.'))
      -&gt;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!