diff --git a/content_entity_example/content_entity_example.info.yml b/content_entity_example/content_entity_example.info.yml index d06c8d0..0315475 100755 --- a/content_entity_example/content_entity_example.info.yml +++ b/content_entity_example/content_entity_example.info.yml @@ -3,6 +3,7 @@ type: module description: 'Provides ContentEntityExampleContact entity.' package: Example modules core: 8.x +# These modules are required by the tests, must be available at bootstrap time dependencies: - options - entity_reference diff --git a/content_entity_example/content_entity_example.links.action.yml b/content_entity_example/content_entity_example.links.action.yml index f843b9c..b307873 100755 --- a/content_entity_example/content_entity_example.links.action.yml +++ b/content_entity_example/content_entity_example.links.action.yml @@ -1,6 +1,11 @@ +# All action links for this module + content_entity_example.contact_add: + # Which route will be called by the link route_name: content_entity_example.contact_add title: 'Add Contact' + + # Where will the link appear, defined by route name. appears_on: - content_entity_example.contact_list - content_entity_example.contact_view diff --git a/content_entity_example/content_entity_example.links.menu.yml b/content_entity_example/content_entity_example.links.menu.yml index d732d65..19693f4 100755 --- a/content_entity_example/content_entity_example.links.menu.yml +++ b/content_entity_example/content_entity_example.links.menu.yml @@ -1,3 +1,5 @@ +# Define the menu links for this module + content_entity_example.contact_list: title: 'Content Entity Example: Contacts Listing' route_name: content_entity_example.contact_list diff --git a/content_entity_example/content_entity_example.links.task.yml b/content_entity_example/content_entity_example.links.task.yml index d18022e..2af3592 100755 --- a/content_entity_example/content_entity_example.links.task.yml +++ b/content_entity_example/content_entity_example.links.task.yml @@ -1,3 +1,5 @@ +# Define the 'local' links for the module + contact.settings_tab: route_name: content_entity_example.contact_settings title: 'Settings' diff --git a/content_entity_example/content_entity_example.module b/content_entity_example/content_entity_example.module index 0fb32d0..1bded97 100755 --- a/content_entity_example/content_entity_example.module +++ b/content_entity_example/content_entity_example.module @@ -15,11 +15,22 @@ use Symfony\Component\HttpFoundation\Request; * * This module demonstrates implementing a Content Entity. * - * This is an example of a fieldable content entity used to hold structured information - * without the overhead of using a content type. It is defined programmatically and we will expose - * the main techniques to handle and expose the contents. + * This is an example of a fieldable content entity used to hold structured + * information without the overhead of using a content type. It is defined + * programmatically and we will expose the main techniques to handle and expose + * the contents. * - * We define a content entity named 'Contact'. + * We define a content entity named 'Contact'. With it, we demonstrate the main + * tasks for an entity: + * - define + * - save + * - load + * - view + * - edit + * - delete + * - control access + * + * Where ever possible, we use the amazing tools built into D8 natively. * } */ @@ -59,4 +70,4 @@ function content_entity_example_help($route_name) { ) ) . '

'; } -} \ No newline at end of file +} diff --git a/content_entity_example/content_entity_example.routing.yml b/content_entity_example/content_entity_example.routing.yml index 43c5273..91f6bbd 100755 --- a/content_entity_example/content_entity_example.routing.yml +++ b/content_entity_example/content_entity_example.routing.yml @@ -1,22 +1,30 @@ +# This file brings everything together. Very nifty! + +# Route name can be used in sevaral place (links, redirects, local actions etc.) content_entity_example.contact_view: path: '/content_entity_example_contact/{content_entity_example_contact}' defaults: + # Calls the view controller, defined in the annotation of the contact entity _entity_view: 'content_entity_example_contact' _title: 'Contact Content' requirements: + # Calls the access controller of the entity, $operation 'view' _entity_access: 'content_entity_example_contact.view' content_entity_example.contact_list: path: '/content_entity_example_contact/list' defaults: + # Calls the list controller, defined in the annotation of the contact entity. _entity_list: 'content_entity_example_contact' _title: 'Contact List' requirements: + # Checks for permission directly. _permission: 'view contact entity' content_entity_example.contact_add: path: '/content_entity_example_contact/add' defaults: + # Calls the form.add controller, defined in the contact entity. _entity_form: content_entity_example_contact.add _title: 'Add Contact' requirements: @@ -25,6 +33,7 @@ content_entity_example.contact_add: content_entity_example.contact_edit: path: '/content_entity_example_contact/{content_entity_example_contact}/edit' defaults: + # Calls the form.edit controller, defined in the contact entity. _entity_form: content_entity_example_contact.edit _title: 'Edit Contact' requirements: @@ -33,15 +42,17 @@ content_entity_example.contact_edit: content_entity_example.contact_delete: path: '/contact/{content_entity_example_contact}/delete' defaults: + # Calls the form.delete controller, defined in the contact entity. _entity_form: content_entity_example_contact.delete _title: 'Delete Contact' requirements: + _entity_access: 'content_entity_example_contact.delete' content_entity_example.contact_settings: path: 'admin/structure/content_entity_example_contact_settings' defaults: - _form: '\Drupal\content_entity_example\Form\ContactSettingsForm' - _title: 'Contact Settings' + _form: '\Drupal\content_entity_example\Form\ContactSettingsForm' + _title: 'Contact Settings' requirements: _permission: 'administer contact entity' diff --git a/content_entity_example/src/ContactAccessController.php b/content_entity_example/src/ContactAccessController.php index 73a9348..1b2c4ee 100755 --- a/content_entity_example/src/ContactAccessController.php +++ b/content_entity_example/src/ContactAccessController.php @@ -20,6 +20,9 @@ class ContactAccessController extends EntityAccessController { /** * {@inheritdoc} + * + * Link the activities to the permissions. checkAccess is called with the + * $operation as defined in the routing.yml file. */ protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) { @@ -43,10 +46,11 @@ class ContactAccessController extends EntityAccessController { /** * {@inheritdoc} + * + * Separate from the checkAccess because the entity does not yet exist, it + * will be created during the 'add' process. */ protected function checkCreateAccess(AccountInterface $account, array $context, $entity_bundle = NULL) { return $account->hasPermission('add contact entity'); } } - - diff --git a/content_entity_example/src/Entity/Contact.php b/content_entity_example/src/Entity/Contact.php index 8128d48..709cb52 100755 --- a/content_entity_example/src/Entity/Contact.php +++ b/content_entity_example/src/Entity/Contact.php @@ -17,9 +17,62 @@ use Drupal\user\UserInterface; * * @ingroup content_entity_example * + * This is the main definition of the entity type. From it, an entityType is + * derived. The most important properties in this example are: + * + * - id: The unique identifier of this entityType. It follows the + * pattern 'moduleName_xyz' to avoid naming conflicts. + * + * - label: Human readable name of the entity type. + * + * - controllers: Controller classes are used for different tasks. You can use + * standard controllers provided by D8 or build your own + * controller, most probably derived from the standard class. + * + * view_builder: we use the standard controller to view an + * instance. It is called when a route lists an + * '_entity_view' default for the entityType + * (see routing.yml for details. The view can be + * manipulated by using the standard drupal tools + * in the settings. + * list builder: We derive out own list builder class from the + * entityListBuilder to control the presentation. + * If there is a view available for this entity + * from the views module, it overrides the list + * builder. @todo: any view? naming convention? + * form: We derive our own forms to add functionality + * like additional fields, redirects etc. + * These forms are called when the routing list + * an '_entity_form' default for the entityType. + * Depending on the suffix (.add/.edit/.delete) + * in the route, the correct form is called. + * access: Our own accessController where we determine + * access rights based on permissions. + * + * - base_table: Define the name of the table used to store the data. Make + * sure it is unique. The schema is automatically determined + * from the BaseFieldDefinitions below. The table is + * automatically created during installation. + * + * - fieldable: Can additional fields be added to the entity via the GUI? + * Analog to content types. + * + * - entity_keys:How to access the fields. Analog to 'nid' or 'uid'. + * + * - links: Provide links to do standard tasks. The 'edit-form' and + * 'delete-form' links are added to the list built by the + * entityListController. They will show up as action buttons in + * an additional column. @todo: understand and explain the admin + * link thing + * + * There are many more properties to be used in an entity type definition. For + * a complete overview, please refer to the '\Drupal\Core\Entity\EntityType' + * class definition. + * + * * @ContentEntityType( * id = "content_entity_example_contact", - * label = @Translation("ContentEntityExample entity"), + * label = @Translation("Contact entity"), * controllers = { * "view_builder" = "Drupal\Core\Entity\EntityViewBuilder", * "list_builder" = "Drupal\content_entity_example\Entity\Controller\ContactListController", @@ -45,11 +98,30 @@ use Drupal\user\UserInterface; * "delete-form" = "content_entity_example.contact_delete" * } * ) + * + * The 'Contact' class defines methods and fields for the contact entity. + * + * Being derived from the ContentEntityBase class, we can override the methods + * we want. In our case we want to provide access to the standard fields about + * creation and changed time stamps. + * + * Our interface (see ContactInterface) also exposes the EntityOwnerInterface. + * This allows us to provide methods for setting and providing ownership + * information. + * + * The most important part is the definitions of the field properties for this + * entity type. These are of the same type as fields added through the GUI, but + * they can by changed in code. In the definition we can define if the user with + * the rights privileges can influence the presentation (view, edit) of each + * field. */ class Contact extends ContentEntityBase implements ContactInterface { /** * {@inheritdoc} + * + * When a new entity instance is added, make sure that the user_id entity + * reference points to the current user as the creator of the instance. */ public static function preCreate(EntityStorageInterface $storage_controller, array &$values) { parent::preCreate($storage_controller, $values); @@ -104,18 +176,32 @@ class Contact extends ContentEntityBase implements ContactInterface { /** * {@inheritdoc} + * + * Define the field properties here. + * + * Field name, type and size determine the table structure. + * + * In addition, we can define how the field and its content can be manipulated + * in the GUI. The behaviour of the widgets used can be determined here. */ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { + + // Standard field, used as unique if primary index. $fields['id'] = FieldDefinition::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'] = FieldDefinition::create('uuid') ->setLabel(t('UUID')) ->setDescription(t('The UUID of the Contact entity.')) ->setReadOnly(TRUE); + // Name field for the contact. + // We set display options for the view as well as the form. + // Users with correct privileges can change the view and edit configuration. + $fields['name'] = FieldDefinition::create('string') ->setLabel(t('Name')) ->setDescription(t('The name of the Contact entity.')) @@ -156,6 +242,11 @@ class Contact extends ContentEntityBase implements ContactInterface { ->setDisplayConfigurable('form', TRUE) ->setDisplayConfigurable('view', TRUE); + // Gender field for the contact. + // ListTextType with a drop down menu widget. + // The values shown in the menu are 'male' and 'female'. + // In the view the field content is shown as string. + // In the form the choices are presented as options list. $fields['gender'] = FieldDefinition::create('list_text') ->setLabel(t('Gender')) ->setDescription(t('The gender of the Contact entity.')) @@ -177,6 +268,10 @@ class Contact extends ContentEntityBase implements ContactInterface { ->setDisplayConfigurable('form', TRUE) ->setDisplayConfigurable('view', TRUE); + // Owner field of the contact. + // Entity reference field, holds the reference to the user object. + // The view shows the user name field of the user. + // The form presents a autocomplete field for the user name. $fields['user_id'] = FieldDefinition::create('entity_reference') ->setLabel(t('User Name')) ->setDescription(t('The Name of the associated user.')) diff --git a/content_entity_example/src/Entity/Controller/ContactListController.php b/content_entity_example/src/Entity/Controller/ContactListController.php index 85ecaf3..c4c9f30 100755 --- a/content_entity_example/src/Entity/Controller/ContactListController.php +++ b/content_entity_example/src/Entity/Controller/ContactListController.php @@ -19,6 +19,11 @@ class ContactListController extends EntityListBuilder { /** * {@inheritdoc} + * + * Building the header and content lines for the contact list. + * + * Calling the parent::buildHeader() adds a column for the possible actions + * and inserts the 'edit' and 'delete' links as defined for the entity type. */ public function buildHeader() { $header['id'] = t('ContactID'); diff --git a/content_entity_example/src/Form/ContactDeleteForm.php b/content_entity_example/src/Form/ContactDeleteForm.php index b555b4d..4ee1292 100755 --- a/content_entity_example/src/Form/ContactDeleteForm.php +++ b/content_entity_example/src/Form/ContactDeleteForm.php @@ -28,6 +28,8 @@ class ContactDeleteForm extends ContentEntityConfirmFormBase { /** * {@inheritdoc} + * + * If the delete command is canceled, return to the contact list. */ public function getCancelURL() { return new Url('content_entity_example.contact_list'); @@ -42,11 +44,17 @@ class ContactDeleteForm extends ContentEntityConfirmFormBase { /** * {@inheritdoc} + * + * Delete the entity and log the event. log() replaces the watchdog. */ public function submit(array $form, FormStateInterface $form_state) { $this->entity->delete(); - watchdog('content', '@type: deleted %title.', array('@type' => $this->entity->bundle(), '%title' => $this->entity->label())); + \Drupal::logger('content_entity_example')->log(WATCHDOG_INFO, '@type: deleted %title.', + array( + '@type' => $this->entity->bundle(), + '%title' => $this->entity->label(), + )); $form_state->setRedirect('content_entity_example.contact_list'); } diff --git a/content_entity_example/src/Form/ContactSettingsForm.php b/content_entity_example/src/Form/ContactSettingsForm.php index 6783092..2ce56e8 100755 --- a/content_entity_example/src/Form/ContactSettingsForm.php +++ b/content_entity_example/src/Form/ContactSettingsForm.php @@ -16,7 +16,7 @@ use Drupal\Core\Form\FormStateInterface; */ class ContactSettingsForm extends FormBase { /** - * Returns a unique string idenying the form. + * Returns a unique string identifying the form. * * @return string * The unique string identifying the form.