Entities in Drupal 7 & the Entity API #sfdug March 11, 2013 JD Leonard (@drupal_jd) ModernBizConsulting.com About Me Computer science background Working with Drupal since 2006 Developer, architect, project manager Freelance through my business: Modern Biz Consulting Focus on complex web application development Agenda Ways we represent data in D6 vs D7 Entities, entity types, & bundles Fields (formerly CCK) The Entity API contrib module How (and why) to define a custom entity Using EntityFieldQuery Defining entity property info Leveraging the Entity Metadata Wrapper Poll Who here is... – Brand new to Drupal? – A site builder? – A module developer? – On the business side of things? Who here has... – Created a custom D7 entity? – Not created a custom D7 entity? D6 Data (Custom database table) User Comment File Taxonomy vocabulary Taxonomy term Node – Page, Blog post, (custom content type) D7 Data (Custom database table) Entity – User – Comment – File – Taxonomy vocabulary – Taxonomy term – Node • Page, Blog post, (custom content type) (Custom entity type) Entity Types Elemental building block for most important data in Drupal 7 Represents a concept or noun Different types store different data – Generally in a single DB table – Eg: Node • – Eg: User • Title, body, created timestamp, etc. Username, created timestamp, last visit timestamp, etc. Defined using hook_entity_info() Entity Instance of an entity type Examples of entities from core: – The user jdleonard with uid 80902 – The page “Drupal Rocks” with nid 44 Any entity can be loaded with entity_load() – Eg: entity_load(‘node’, array(44, 65)) • – Returns an array with two nodes with nids 44 and 65 Core provides some convenience wrappers • Eg: node_load() and user_load() Entity Bundles Subtype of an entity Eg: Page and Blog post – Two content types (bundles) of node entity type Not all entity types have more than one bundle – Eg: User • No subtypes; concept stands on its own Why We Need Entity Bundles Each entity type stores some data in a table – We call these pieces of data “properties” – Eg: node • • Title, body, author (uid), created timestamp But not all nodes are created equal – – – Page may have data beyond that captured by node Blog posts get tagged We need fields! CCK: Content Construction Kit (D6) Contrib module Store additional data per content (node) content type Eg: Fivestar – Contrib module leveraging contrib CCK API – Allows nodes to be rated (think 1-5 stars) – Eg: let users rate pages or blog posts Fields (D7) Core concept Store additional data per entity type Applies power of CCK to all entity types – Not just nodes! Eg: Fivestar – Contrib module leveraging core Field API – Allows entities to be rated – Eg: let users rate pages, blog posts, users, comments, custom entities Entities in Core vs Contrib Entities are a Drupal 7 core concept Core provides the “Entity API” – Functions and hooks to define and interact with entities, entity types, and entity bundles Everything we've discussed so far is part of Drupal 7 core (no contrib modules necessary) Not everything “made it into” core – Thankfully there's the “Entity API” contrib module • (confusingly named) Entity API Contrib Module Makes dealing with entities easier Provides full CRUD functionality – CReate, Update, and Delete Allows definition of metadata about entities Optionally makes entity data – Exportable (for configuration) – Revisionable (eg: node revisions) Optional administrative UI for managing entities Object-oriented representation of entity types A Note on Documentation Documentation for core's Entity API is separate from that for the Entity API contrib module In practice, you'll probably always use the Entity API contrib module – It provides so much awesome functionality! Don't get confused – Always install the Entity API contrib module when defining an entity – Make sure to read the module's documentation Contrib Modules Defining Entities Commerce – Organic Groups – Rules Configuration Message (think activity stream) – OG Membership, OG Membership Type Rules – Commerce Product, Commerce Payment Transaction Message, Message Type, Message Type Category … and many more! Example Custom Entity Type TextbookMadness.com – Classified listings for textbooks at schools – Online price comparison shopping (prices from Amazon, Textbooks.com, etc.) Online prices are – Fetched from a third-party API and stored by Drupal – Hereafter known as Offers How Shall we Define an Offer? We could use the UI to define an offer node with a bunch of fields to store pricing information But there's a lot of overhead! – Don't need/want a page (path) per offer – Don't need/want revisions – Don't need/want node base table information • Language (and translation info), Title, Author, Published status, Created date, Commenting, Promoting, etc. We want an entity! Implement hook_schema() $schema['tbm_offer'] = array( // SIMPLIFIED FOR SLIDE 'fields' => array( 'offer_id' => array('type' => 'serial'), 'isbn' => array('type' => 'int'), 'retailer_id' => array('type' => 'int'), 'price' => array('type' => 'int'), 'last_updated' => array('type' => 'int'), ), 'primary key' => array('offer_id') ); Implement hook_entity_info() $entities['tbm_offer'] = array( 'label' => t('Offer'), 'plural label' => t('Offers'), 'entity class' => 'Entity', 'controller class' => 'EntityAPIController', 'module' => 'tbm', 'base table' => 'tbm_offer', 'fieldable' => FALSE, 'entity keys' => array( 'id' => 'offer_id', )); Make an Entity Type Exportable $entities['tbm_offer'] = array( 'label' => t('Offer'), 'plural label' => t('Offers'), 'entity class' => 'Entity', 'controller class' => 'EntityAPIControllerExportable', 'module' => 'tbm', 'base table' => 'tbm_offer', 'fieldable' => FALSE, 'exportable' => TRUE, 'entity keys' => array( 'id' => 'offer_id', 'name' => 'my_machine_name' )); Make an Entity Type Revisionable $entities['tbm_offer'] = array( 'label' => t('Offer'), 'plural label' => t('Offers'), 'entity class' => 'Entity', 'controller class' => 'EntityAPIController', 'module' => 'tbm', 'base table' => 'tbm_offer', 'revision_table' => 'tbm_o_revision', 'fieldable' => FALSE, 'entity keys' => array( 'id' => 'offer_id', 'revision' => 'revision_id', )); Other hook_entity_info() configuration $entities['tbm_offer'] = array( … 'entity class' => 'TbmOfferEntity', // Extends Entity class 'controller class' => 'TbmOfferEntityController', 'access callback' => 'tbm_offer_access', 'admin ui' => array( 'path' => 'admin/structure/offers', 'file' => 'tbm.admin.inc', ), 'label callback' => 'entity_class_label', 'uri callback' => 'entity_class_uri', ); // TbmOfferEntity->defaultLabel(), defaultUri() Entity Property Info hook_entity_property_info() – Defines entity metadata – Provided for core entities Automatically generated from hook_schema() – Doesn't understand foreign key relationships (references) – Doesn't know when an integer is actually a date (eg: unix timestamps) Fill in the gaps with hook_entity_property_info_alter() hook_entity_property_info_alter() $offer = &$info['tbm_offer']['properties']; $offer['url']['type'] = 'uri'; // was a varchar in hook_schema $offer['url']['label'] = 'URL'; $offer['last_updated']['type'] = 'date'; // integer in hook_schema $offer['last_updated']['label'] = t('Last updated'); // Other types: text, token (machine name), integer, decimal, duration, boolean, entity, struct, list<TYPE> hook_entity_property_info_alter() // Define a new property to reference an offer's retailer (eg: Amazon.com) $offer['retailer'] = array( 'label' => t('Retailer'), 'type' => 'tbm_retailer', // The tbm_retailer entity type 'description' => t('The retailer making the offer.'), 'required' => TRUE, 'schema field' => 'retailer_id', // as defined in hook_schema ); Entity API Integrations Views – Eg: in a view of offers, we can add a retailer relationship and include details about each offer's retailer Rules Search API Features I18n Token system - Eg: [tbm_offer:retailer:name] EntityFieldQuery Tool for querying entities Can query entity properties and field data Can query field data across entity types – Eg: “return all pages and users tagged with taxonomy term 456” Returns entity IDs – Usually you’ll then load with entity_load() EntityFieldQuery Example $query = new EntityFieldQuery(); $query ->entityCondition('entity_type', 'node') ->entityCondition('bundle', 'page') ->propertyCondition('status', 1) ->fieldCondition('field_awesome_factor', 'value', 3.2, '<') ->fieldOrderBy('field_awesome_factor', 'value', 'DESC') ->range(0, 10); // Top 10 Published page nodes with an awesome factor < 3.2 EntityFieldQuery Example $result = $query->execute(); // Keyed by entity type if (isset($result['node'])) { $nids = array_keys($result['node']); $pages = node_load_multiple($nids); $same_as_pages = entity_load('node', $nids); } Entity Metadata Wrapper Examples // Get node author's email address (given $nid) // BEFORE $node = node_load($nid); $author = user_load($node->uid); $email = check_plain($author->mail); // AFTER $wrapper = entity_metadata_wrapper('node', $nid); $email = $wrapper->author->mail->value(); Entity Metadata Wrapper Examples // Set node author's email address $wrapper->author->mail = 'me@example.com'; $wrapper->save(); // Iterate over a list of node's taxonomy terms foreach ($wrapper->field_taxonomy_terms->getIterator() as $term_wrapper) { $label = $term_wrapper->label->value(); } More Modules! Entity Construction Kit (ECK) – CCK for entities – Create entity types in a UI Entity Cache – Entity Reference – Integrates entities with Drupal's Cache API Like node reference field, but for any type of entity Automatic Entity Label – Generic successor to Automatic Node Titles More information Entity API documentation – Lots of information in many subpages EntityFieldQuery documentation Entity API contrib module page Deck is on Slideshare Email me: jd at ModernBizConsulting.com Follow me: @drupal_jd