Chapter 4. Drupal Programming Examples
Now that you have learned the basic principles of Drupal programming and how to avoid making common Drupal programming mistakes, it’s time to put your programming skills to work! This chapter covers special topics in Drupal programming that you can use to enhance websites built with Drupal; they’re all things I’ve actually needed to do either in my freelance work as a Drupal site builder or my volunteer work programming for the Drupal project (for Drupal core and contributed modules). My advice is to skim this chapter now so you know what it covers and then read individual sections in more detail when you need them.
Other places to look for programming examples:
-
The Drupal core code itself, which includes extensive documentation and tests
-
Examples for Developers, which has comprehensive coverage of the Drupal core APIs and how to use them in your own modules
-
Thousands of GPL-licensed contributed modules you can download from https://www.drupal.org/project/modules and then adapt for your own work
-
The API reference site, https://api.drupal.org; see also “Using api.drupal.org”
Registering for URLs and Displaying Content
“How Drupal Handles HTTP Requests” contains overviews of how Drupal 7 and Drupal 8 handle URL requests and return content to a web browser or other requester. This section goes into more detail about how a module you write can be part of that process, by registering with Drupal to handle specific URLs and by providing page and block content.
Warning
Given that Drupal has many hooks and that it is written in PHP, there are many ways that you could consider writing code for Drupal that would return output in response to a URL request, or that would place content in a region on a page or set of pages. Most of these ways would, however, amount to subverting Drupal’s standard processes, rather than working within the Drupal framework. Use the methods in this section of the book for best results.
Assuming that you have decided you need your module to output some content, the first choice you need to make is whether to provide a router entry or a block. A router entry allows you to respond to a URL request by providing the main HTML content for that page or, in some cases, the entire output for the URL (such as XML output that is used by a Flash script on your site, an RSS feed, or an Ajax response). A block allows you to provide a chunk of output that can be placed on one or more of a site’s HTML-based pages. In either case, you will need to register with Drupal for the block or URL, and then write a function or class to generate the output; in the case of a router entry, you will also need to define permissions for accessing the URL. All of these steps are described in the following sections.
Note that you should write code to provide blocks and router entries only if there is some logic or programming needed to generate the content of the block or page. If you want to display static content on a website, you can create a block or content item using Drupal’s user interface, and if you need to employ complicated logic to decide where or when to show the block, use the Context module or Panels module. Also, keep in mind that using the Views module is a better choice than making a custom module in many cases.
Further reading and reference:
-
“Mistake: Programming Too Much” (which also lists the modules mentioned here)
Providing Administrative Links
In Drupal 7, when you use hook_menu()
to register for a path under admin,
you will also automatically be creating an administrative menu link. For
instance, if you register a path admin/structure/mymodule and navigate to
the Structure administrative page at admin/structure, your page will be
listed; you’ll also be able to see it in the Management menu hierarchy.
In Drupal 8, menu-entry creation has been decoupled from route registration, so
using a *.routing.yml file to register a route
does not automatically register an administrative
menu link. In order to register an administrative menu link for a
route you’ve already defined, you’ll need to create a mymodule.links.menu.yml
file in your main module directory. In this
file, you’ll tell Drupal where to place the menu entry (by giving the machine
name of the parent
link—not the route name of the parent link, although
they are often the
same). You’ll also need to provide your route
machine name (which will tell Drupal the URL path of your link), the link title,
and the description. For instance, you could make the route defined in the
previous section appear in the Structure menu section with this entry in mymodule.links.menu.yml:
mymodule.mydescription: title: 'Configure My Module' description: 'Longer description goes here' parent: system.admin_structure route_name: mymodule.mydescription
In some cases, you’ll want to provide a local task (tab) for an existing
administrative page, rather than adding your own administrative menu
entry. In Drupal 7, this is also taken care of in hook_menu()
, by making an
entry that is of type MENU_LOCAL_TASK
instead of the default
MENU_NORMAL_ITEM
. Here’s an example from the core User module, which makes the
Permissions page be a local task for the People list page:
$items
[
'admin/people/permissions'
]
=
array
(
'title'
=>
'Permissions'
,
'description'
=>
'Determine access to features.'
,
'page callback'
=>
'drupal_get_form'
,
'page arguments'
=>
array
(
'user_admin_permissions'
),
'access arguments'
=>
array
(
'administer permissions'
),
'file'
=>
'user.admin.inc'
,
'type'
=>
MENU_LOCAL_TASK
,
);
In Drupal 8, the creation of a local task to be displayed on a given page is accomplished by creating a mymodule.links.task.yml file in your main module directory, giving the route name for the task page, the tab title, and the base route name (the route name of the page that should display the task). For this same User module Permissions page, the core/modules/user/user.links.task.yml file contains:
user.admin_permissions: title: Permissions route_name: user.admin_permissions base_route: entity.user.collection
Besides local tasks, there are also local actions. Local tasks are
usually rendered as tabs at the tops of pages, while local actions are usually
rendered as buttons. A typical use for a local action is an “add” button; for
example, the Drupal core User module has an “Add user” button on the People
administration page. This is accomplished in Drupal 7 with the following code in
its hook_menu()
implementation:
$items
[
'admin/people/create'
]
=
array
(
'title'
=>
'Add user'
,
'page arguments'
=>
array
(
'create'
),
'access arguments'
=>
array
(
'administer users'
),
'type'
=>
MENU_LOCAL_ACTION
,
);
In Drupal 8, local actions are defined in the mymodule.links.action.yml file in the main module directory. For this same Add user button, the core/modules/user/user.links.action.yml file contains:
user_admin_create: route_name: user.admin_create title: 'Add user' appears_on: - entity.user.collection
Note that the appears_on
entry is the name of the route for the page the local
action should appear on.
The final type of administration link that you can define is a contextual link, which gives users context-based operations. They are a little bit more complex to implement than local tasks and actions, because they operate on some specific piece of data and have to be collected and added to administrative pages that display that type of data.
In Drupal 7, again contextual links are part of the hook_menu()
system,
specified by giving a context
element to a local task entry. Here’s the
section of block_menu()
from the core Block module
that sets up the Configure link for a block:
$items
[
'admin/structure/block/manage/%/%/configure'
]
=
array
(
'title'
=>
'Configure block'
,
'type'
=>
MENU_DEFAULT_LOCAL_TASK
,
'context'
=>
MENU_CONTEXT_INLINE
,
);
In Drupal 8, the entity system sets up contextual links automatically for content entities; if you want to have contextual links for non-entity data, here are the steps that Drupal core uses to provide this same contextual link.
First, choose a group name for your contextual links, which
needs to be unique and should describe what type of data the links operate
on. In our Block module example, there is a group called block
for
contextual links that operate on blocks.
Second, in your mymodule.links.contextual.yml file, define the operations on either a group that your module has defined or one from another module. In the Block example, the Configure link is defined in its block.links.contextual.yml file:
block_configure: title: 'Configure block' route_name: 'entity.block.edit_form' group: 'block'
Here, the first line defines the machine name of the contextual link, and the other lines give the group machine name, the link title, and the route name for making the link.
Third, on the administrative page that displays your data, add a
#contextual_links
element to a render array, to render the links. In the
Block example, \Drupal\block\BlockViewBuilder::viewMultiple()
has these lines:
$build
[
$entity_id
][
'#contextual_links'
]
=>
array
(
'block'
=>
array
(
'route_parameters'
=>
array
(
'block'
=>
$entity
->
id
()),
),
);
This tells the Contextual Links module (if installed) to collect all the
contextual links in the block
group and render them, passing in the route
parameter from the current block to construct each link. If the Contextual Links
module is not installed, this part of the render array will be ignored.
If you want to alter the menu links, local tasks, contextual links, or local
actions defined by
another module, you can do that with an alter hook. In Drupal 7, all of
these are defined in hook_menu()
, so use hook_menu_alter()
to alter them. In
Drupal 8, use hook_menu_links_discovered_alter()
to alter the administrative
menu
links, hook_menu_local_tasks_alter()
to alter the local tasks,
hook_menu_local_actions_alter()
to alter the local actions, and
hook_contextual_links_alter()
to alter contextual links. For local tasks,
you can also implement hook_menu_local_tasks()
to provide additional, dynamic
local tasks; this hook runs before the alter hook.
Further reading and reference:
-
hook_menu_alter()
in Drupal 7: “Altering a URL Registration in Drupal 7”
Examples—administrative links:
Altering Routes and Providing Dynamic Routes in Drupal 8
Drupal 8’s event system is used for dynamic routes and route altering:
-
Dynamic routes are for URL paths that cannot be set up as static routes in a routing.yml file. For example, the core Field UI module provides field management pages (Manage Fields, Manage Display, and Manage Form Display) for each entity type and subtype that supports fields and administrative management. Because the Field UI module does not know what entity types will be available on a given Drupal site, and because each entity type has full flexibility and control over its administrative URL path, there is no way for the Field UI module to set these up as static routes in a YAML file. Instead, at runtime, it needs to set up the needed routes for defined entity types.
-
Route altering is when a module changes some aspect of a route that another module has set up. As an example, if the core Editor module is enabled, it changes the names and descriptions of the Filters administrative page, because editors are managed on the same page.
To provide dynamic routes or alter other modules’ routes:
-
Create an event subscriber class. This class must implement
\Symfony\Component\EventDispatcher\EventSubscriberInterface
; the easiest way to do that is to extend\Drupal\Core\Routing\RouteSubscriberBase
, as it sets up all of the needed framework for you. Put it into the\Drupal\mymodule\Routing
namespace, so if the class name isMyModuleRouting
, it must go into the src/Routing/MyModuleRouting.php file under your main module directory. Be sure to includeuse
statements for the\Symfony\Component\Routing\Route
,\Symfony\Component\Routing\RouteCollection
, and\Drupal\Core\Routing\RouteSubscriberBase
classes. -
In your class, override the
alterRoutes()
method, which operates on the route collection (the list of all of the routes that are set up by all modules). Here are some examples:
protected
function
alterRoutes
(
RouteCollection
$collection
)
{
// Alter the title of the People administration page (admin/people).
$route
=
$collection
->
get
(
'entity.user.collection'
);
$route
->
setDefault
(
'_title'
,
'User accounts'
);
// Make sure that the title text is translatable.
$foo
=
t
(
'User accounts'
);
// Add a dynamic route at admin/people/mymodule, which could have been
// a static route in this case.
$path
=
$route
->
getPath
();
// Constructor parameters: path, defaults, requirements, as you would have
// in a routing.yml file.
$newroute
=
new
Route
(
$path
.
'/mymodule'
,
array
(
'_controller'
=>
'\Drupal\mymodule\Controller\MyUrlController::generateMyPage'
,
'_title'
=>
'New page title'
,
),
array
(
'_permission'
=>
'administer mymodule'
,
));
// Make sure that the title text is translatable.
$foo
=
t
(
'New page title'
);
$collection
->
add
(
'mymodule.newroutename'
,
$newroute
);
}
-
Because this example is altering routes from the User module, declare a dependency on this module in the mymodule.info.yml file:
dependencies: - user
-
Put these lines into the mymodule.services.yml file in the main module directory (omit the first line if you already have services in the file):
services: mymodule.subscriber: class: Drupal\mymodule\Routing\MyModuleRouting tags: - { name: event_subscriber }
-
After adding a new route subscriber, or after updating what it does, you will need to clear the Drupal cache so that Drupal will rebuild its cached routing information.
Registering a Block in Drupal 7
If you want to provide content that can be displayed on multiple pages, you
should register for a block rather than for a path in your module.
To register for a block in Drupal 7, you need to implement hook_block_info()
in your mymodule.module file to tell Drupal about the existence of your block,
and then implement hook_block_view()
to generate the block content. For
example:
// Tell Drupal about your block.
function
mymodule_block_info
()
{
$blocks
=
array
();
// The array key is known as the block "delta" (a unique identifier
// within your module), and is used in other block hooks. Choose
// something descriptive.
$blocks
[
'first_block'
]
=
array
(
// The name shown on the Blocks administration page.
// Be descriptive and unique across all blocks.
'info'
=>
t
(
'First block from My Module'
),
);
return
$blocks
;
}
// Generate the block content. Note that the $delta value passed in
// is the same as the array key you returned in your hook_block_info()
// implementation.
function
mymodule_block_view
(
$delta
=
''
)
{
if
(
$delta
==
'first_block'
)
{
return
array
(
// The block's default title.
'subject'
=>
t
(
'First block'
),
// The block's content.
'content'
=>
mymodule_block_generate
(),
);
}
}
Notes:
-
Implementations of
hook_block_info()
can be more complex than this: they can specify cache parameters (block output is cached by default for efficiency) and default placement. -
Blocks can also have configuration settings. These are provided by implementing
hook_block_configure()
andhook_block_save()
. -
The
hook_block_view()
implementation here calls a function (in this example,mymodule_block_generate()
) to provide the actual block content. Because block-generating functions are very similar to page-generating functions, the details of what this function should return are covered in a separate section: “Creating Render Arrays for Page and Block Output”. -
After adding a new block to a
hook_block_info()
implementation, you will need to clear the Drupal cache to make it visible on the Blocks page.
Further reading and reference:
Examples—blocks:
-
Look up
hook_block_info()
on https://api.drupal.org to find all the options and links to the Drupal core functions that implement it. -
The Block example in Examples for Developers and many Drupal core blocks include configuration options, cache settings, and other options.
Registering a Block in Drupal 8
In Drupal 8 (as in Drupal 7), if you want to provide content that can be
displayed on multiple pages, you should register for a block rather than for a
route in your module. Drupal 8 uses a plugin system for blocks, so all you have to
do to register a block in Drupal 8 is properly define a Block plugin
class. Block plugin classes should either extend the \Drupal\Core\Block\BlockBase
class or implement the \Drupal\Core\Block\BlockPluginInterface
interface, and they need
to have Block
plugin annotation from the \Drupal\Core\Block\Annotation\Block
annotation class. They must be in the
\Drupal\mymodule\Plugin\Block
namespace and thus must be located in the
src/Plugin/Block subdirectory under your module directory.
To define the same block as in “Registering a Block in Drupal 7”, a good name for the class
would be MyModuleFirstBlock
, making the file
src/Plugin/Block/MyModuleFirstBlock.php:
namespace
Drupal\mymodule\Plugin\Block
;
use
Drupal\Core\Block\BlockBase
;
/**
* Provides a sample block.
*
* @Block(
* id = "mymodule_first_block",
* admin_label = @Translation("First block from My Module")
* )
*/
class
MyModuleFirstBlock
extends
BlockBase
{
public
function
build
()
{
// Function body will be defined later.
}
}
Notes:
-
The
@Block
annotation provides a unique identifier for the block, as well as the name for the block that will appear on the Blocks administration page. See “The Basics of Drupal 8 Plugin Programming” for more details about plugins and annotation. -
The
build()
method does the work of providing output for the block. Because block-generating functions are very similar to page-generating functions, the details of what this function should return are covered in “Creating Render Arrays for Page and Block Output”. -
You can also implement or override additional methods to provide access control, caching control, and configuration settings.
-
After defining a new block plugin class, you will need to clear the Drupal cache to make it visible on the Blocks page.
Creating Render Arrays for Page and Block Output
Once your module has registered for a URL or block (see previous sections), you need to write a function or a method that returns the page or block output. In many content management systems, and in Drupal 6 and prior versions, output is generated directly and returned as a string of HTML markup and text. The problem with this philosophy is that when a module (rather than the theme) is making HTML markup decisions, it is difficult for the theme to have full control over how data is presented. Accordingly, Drupal 7 and 8 use a different philosophy, where modules that provide output for site visitors should always return the output data and meta-data properties, rather than rendered markup. This data can then be altered by other modules and finally used by the theme system to render the data into HTML. The structure used to return the output data and properties is known as a render array.
Here is the general structure of a render array that you could return from a page- or block-generating function or method:
$output
=
array
(
'sensible_identifier_1'
=>
array
(
'#type'
=>
'element_identifier'
,
// Other properties and data here.
),
'sensible_identifier_2'
=>
array
(
'#theme'
=>
'theme_hook'
,
// Other properties and data here.
),
// Other pieces of output here.
);
Notes:
-
The outermost array keys are arbitrary: choose sensible identifiers that will remind you of what each piece of your block or page is.
-
At the next level of arrays, keys starting with
'#'
are property keys that are recognized by the Render API. You can also provide properties on the outermost array. -
A render array should contain one or more of the following:
-
A
'#type'
property, whose value is the machine name of a render element; see “What Is a Render Element?”. -
A
'#theme'
property, whose value is the name of a theme hook. If used with the'#type'
property, it would override the default theme hook for the render element. -
Keys not starting with
'#'
, whose array values are themselves render arrays. If the parent array does not have a render element type or theme hook specified, each child array will be independently rendered and the output concatenated.
-
-
Theme hooks are defined by modules by implementing
hook_theme()
, and each theme hook also requires one or more properties to be provided. -
Be sure that all your text is internationalized.
-
If there is a
'#markup'
property and'#type'
is not set, a render element type ofmarkup
will be assumed.
Here’s an example of a render array that has an informational paragraph,
followed by a list of items, followed by a table (the paragraph uses a
'markup'
render element; the list and table use the 'item_list'
and
'table'
theme hooks):
$output
=
array
(
'introduction'
=>
array
(
'#type'
=>
'markup'
,
'#markup'
=>
'<p>'
.
t
(
'General information goes here.'
)
.
'</p>'
,
),
'colors'
=>
array
(
'#theme'
=>
'item_list'
,
'#items'
=>
array
(
t
(
'Red'
),
t
(
'Blue'
),
t
(
'Green'
)),
'#title'
=>
t
(
'Colors'
),
),
'materials'
=>
array
(
'#theme'
=>
'table'
,
'#caption'
=>
t
(
'Materials'
),
'#header'
=>
array
(
t
(
'Material'
),
t
(
'Characteristic'
)),
'#rows'
=>
array
(
array
(
t
(
'Steel'
),
t
(
'Strong'
)),
array
(
t
(
'Aluminum'
),
t
(
'Light'
)),
),
),
);
Generic JavaScript code and files can be added to a render array by using the
'#attached'
property. In Drupal 7, the jQuery library will be attached by
default on every page, so you can
make use of that when writing your JavaScript. Here are some Drupal 7 examples:
// Attach a JavaScript file.
$output
[
'#attached'
][
'js'
][]
=
drupal_get_path
(
'module'
,
'mymodule'
)
.
'/mymodule.js'
;
// Attach some in-line JavaScript code.
$output
[
'#attached'
][
'js'
][]
=
array
(
'type'
=>
'inline'
,
'data'
=>
$my_code
,
);
CSS attachment works the same way, with the 'css'
array element. You can
also define a library that contains one or more JavaScript and CSS files,
and it can depend on other libraries. Look up hook_library_info()
on
https://api.drupal.org for more information on that.
Further reading and reference:
-
The general structure and many of the details of render arrays are the same between Drupal 7 and Drupal 8. Drupal 8 specifics are covered in “Render Arrays in Drupal 8”.
-
More about theme hooks: “Making Your Module Output Themeable”.
-
Internationalizing text: “Principle: Drupal Is International”.
-
Find Drupal core theme hooks on the “Default theme implementations” (Drupal 7) or “Theme system overview” (Drupal 8) topic on https://api.drupal.org.
-
CSS and JavaScript libraries in Drupal 7: http://bit.ly/js_in_drupal7.
-
CSS and JavaScript libraries in Drupal 8: http://bit.ly/css_js_in_drupal8.
Examples—render arrays:
Note
It is still possible in Drupal 7 to return strings from your page and block content functions instead of render arrays. Using render arrays is preferred, however, because:
Render Arrays in Drupal 8
As noted in the previous section, both the general concept and nearly all of the details of render arrays are the same in Drupal 7 and 8. Here are some differences:
-
If your render array is being generated in a class that has a
t()
method, you should call this method in place of the globalt()
function, to internationalize user-interface text. -
In Drupal 8, attaching CSS and JavaScript files has to be done via a library. You’ll need to put all of your JavaScript and CSS into files, and then define a library in a mymodule.libraries.yml file in your main module directory that looks like this:
myjslib: version: 1.x js: mymodule.js : {}
$form
[
'#attached'
][
'library'
][]
=
'mymodule/myjslib'
;
-
Data caching in Drupal 8 is more sophisticated and granular than in Drupal 7, and page caching is done on a more granular level. To enable this, render array elements need to include information about what cache tags and cache contexts they depend on, so that the Drupal rendering system can decide whether to render them during a page load or use a cached version.
Further reading and reference:
Generating Paged Output
If a page or block you are generating output for is listing data, you need to think about what should happen if the list of data gets long; usually you would want the output to be separated into pages. If you are using a database query to generate the list, Drupal’s Database API and theme system make separating the output into pages very easy. Here are the steps:
-
Use a dynamic query rather than a static query. In Drupal 7, this means using
db_select()
rather thandb_query()
. In Drupal 8, this means using theselect()
method on your database connection object rather than thequery()
method. -
In Drupal 7, add the
PagerDefault
extender to your database query. In Drupal 8, it is the\Drupal\Core\Database\Query\PagerSelectExtender
extender. -
Add an entry with
'#theme'
set topager
to your output render array in Drupal 7, or'#type'
set topager
in Drupal 8. This will add a section to your page containing links to the pages of output. Each link will take you to your base page URL, with the URL query parameter ?page=N appended (N starts at 0 for the first page, 1 for the second, etc.). The pager query extender will automatically read this URL query parameter to figure out what page of output the user is on and return the appropriate rows in the database query.
As an example, assume you want to show the titles of the most recently created node content items, and you want to show 10 items per page. You should really use the Views module to do this, but for purposes of illustration, here is the code you would need to put into your output-generating function for the block or page in Drupal 7:
// Find the most recently created nodes.
$query
=
db_select
(
'node'
,
'n'
)
->
fields
(
'n'
,
array
(
'title'
))
->
orderBy
(
'n.created'
,
'DESC'
)
// Be sure to check permissions, and only show published items.
->
addTag
(
'node_access'
)
->
condition
(
'n.status'
,
1
)
// Put this last, because the return value is a new object.
->
extend
(
'PagerDefault'
);
// This only applies with the PagerDefault extender.
$query
->
limit
(
10
);
$result
=
$query
->
execute
();
// Extract the information from the query result.
$titles
=
array
();
foreach
(
$result
as
$row
)
{
$titles
[]
=
check_plain
(
$row
->
title
);
}
// Make the render array for a paged list of titles.
$build
=
array
();
// The list of titles.
$build
[
'items'
]
=
array
(
'#theme'
=>
'item_list'
,
'#items'
=>
$titles
,
);
// The pager.
$build
[
'item_pager'
]
=
array
(
'#theme'
=>
'pager'
);
return
$build
;
In Drupal 8, you should also use the Views module to accomplish this, or an entity query. But for illustration purposes, here is the equivalent code in Drupal 8, assuming you already have a database connection object:
$query
=
$connection
->
select
(
'node'
,
'n'
);
$query
->
innerJoin
(
'node_field_data'
,
'nd'
,
'n.nid = nd.nid AND n.vid = nd.vid'
);
$query
=
$query
->
extend
(
'Drupal\Core\Database\Query\PagerSelectExtender'
)
// Add pager.
->
addMetaData
(
'base_table'
,
'node'
)
// Needed for join queries.
->
limit
(
10
)
// 10 items per page.
->
fields
(
'nd'
,
array
(
'title'
,
'nid'
))
// Get the title field.
->
orderBy
(
'nd.created'
,
'DESC'
)
// Sort by last updated.
->
addTag
(
'node_access'
)
// Enforce node access.
->
condition
(
'nd.status'
,
1
);
$result
=
$query
->
execute
();
// Extract and sanitize the information from the query result.
$titles
=
array
();
foreach
(
$result
as
$row
)
{
$titles
[]
=
$row
->
title
;
}
// Make the render array for a paged list of titles.
$build
=
array
();
$build
[
'items'
]
=
array
(
'#theme'
=>
'item_list'
,
'#items'
=>
$titles
,
);
// Add the pager.
$build
[
'item_pager'
]
=
array
(
'#type'
=>
'pager'
);
return
$build
;
Further reading and reference:
-
It is usually better to use the Views module rather than doing your own page queries: “Avoiding Custom Programming with Fielded Data”.
Using the Drupal Form API
One of the real strengths of Drupal for programmers is the Form API, which is a vast improvement over the process you would see in a standard reference on PHP programming for output and processing of web forms. The first step in using the Drupal Form API is to create a form-generating function (Drupal 7) or a form class with a form-generating method (Drupal 8); this function or method generates a structured form array containing information about the form elements and other markup to be displayed. Then, you will write form validation and submission functions or methods, which are called automatically by Drupal when your form is submitted, allowing you to securely process the submitted data. The advantages of using the Form API over doing all of this in raw HTML and PHP are:
-
You can write a lot less code, as you’re letting Drupal handle all of the standard parts of form creation and submission. For instance, you do not have to write code to read the
$_GET
or$_POST
variable, and you do not have to create a dedicated PHP file with root-level code for the form’saction
attribute. -
Your code will be easier to read and maintain: Drupal’s form arrays are much easier to deal with than raw HTML forms.
-
As with other parts of Drupal, your form will be alterable by other modules, and the exact rendering is controlled by the theme system.
-
When the form is rendered, Drupal adds a unique token to protect against cross-site scripting, and this is validated during form submission.
-
Other security checks are also performed on form submission, such as matching up submitted values to allowed fields, omitting submissions to disabled form elements, and so on.
-
The HTML output of the Drupal Form API is set up to be accessible, with no extra effort required on your part.
Form Arrays, Form State Arrays, and Form State Objects
Form arrays have the same general structure as the render arrays discussed in “Creating Render Arrays for Page and Block Output”—they are a specialized type of render array. (Actually, form arrays predate render arrays in Drupal history, so you could also say that render arrays are a generalization of form arrays.) There are special render element types that should only be used in form arrays, representing HTML form input elements or groups of input elements. Here is an example of a form array:
$form
=
array
();
// Plain text input element for first name.
$form
[
'first_name'
]
=
array
(
'#type'
=>
'textfield'
,
'#title'
=>
t
(
'First name'
),
);
// Plain text element for company name, only visible to some
// users.
$form
[
'company'
]
=
array
(
'#type'
=>
'textfield'
,
'#title'
=>
t
(
'Company'
),
// This assumes permission 'use company field' has been defined.
'#access'
=>
user_access
(
'use company field'
),
);
$my_information
=
'stuff'
;
// Some hidden information to be used later.
$form
[
'information'
]
=
array
(
'#type'
=>
'value'
,
'#value'
=>
$my_information
,
);
// Submit button.
$form
[
'submit'
]
=
array
(
'#type'
=>
'submit'
,
'#value'
=>
t
(
'Submit'
),
);
Notes:
-
The preceding code is for Drupal 7. In Drupal 8, there are only minor changes. First,
user_access()
is replaced by\Drupal::currentUser()->hasPermission()
, or the equivalent call on a dependency-injectedcurrent_user
service. Second, calls to thet()
function should be replaced by thet()
method on the class that is generating the form. -
The type of element is specified in the
'#type'
property. The types supported by Drupal are listed in the Drupal Form API Reference, which also tells which properties each element uses. You can find the Drupal-version-specific Form API Reference on https://api.drupal.org. -
The
value
form element type can be used to pass information to form validation and submission functions. This information is not rendered at all into the form’s HTML, and no input is accepted from these elements (in contrast to form elements of typehidden
, which render as HTMLinput
elements withtype="hidden"
), so they are more secure and can contain any PHP data structure. -
Most elements have
'#title'
properties, which are displayed as form element labels. -
All form elements have an
'#access'
property. If it evaluates toTRUE
or is omitted, the element is shown; if it evaluates toFALSE
, the element is not displayed. In Drupal 8, you can alternatively provide an'#access_callback'
property, which is a PHP callable (or function name), which is called to determine access (the element being access checked is passed in as the sole argument). -
You can include
markup
and other render elements in your form arrays.
The form generation, validation, and submission functions or methods receive
information about the form submission in a form state variable, $form_state
,
which is retained through the whole process.
In Drupal 7, $form_state
is an array; in Drupal 8, it is an object that
implements \Drupal\Core\Form\FormStateInterface
.
You can add custom information to the form state during any step for later
use. In Drupal 7, add information directly as array elements in
$form_state
, and retrieve it later from the
array. In Drupal 8, use the set()
method to add information and get()
to
retrieve it later.
The main piece of information you will use from $form_state
is
the values entered in the form elements by the user, which are in
$form_state['values']
in Drupal 7, and retrieved by a call to
the getValues()
method in Drupal 8. Either way, the submitted values are an
array, keyed by the same keys used in the form array for the form input
elements.
One difficulty in the values array is that if the form has a nested structure,
the values can be either nested or flat, depending on the '#tree'
property of
the form as a whole, or any parent subarray. Because this would make extracting
form values complicated, after the initial processing step Drupal’s Form API
provides a '#parents'
property,
on each element or subelement in a form array, which is an array giving the
nested parents needed to find the submitted values in the values array.
Assuming that $parents
holds this property for the element you’re trying to
get the value of, you can extract the value by calling:
// Drupal 7
$value
=
drupal_array_get_nested_value
(
$form_state
[
'values'
],
$parents
);
// Drupal 8
$values
=
$form_state
->
getValues
();
$value
=
\Drupal\Component\Utility\NestedArray
::
getValue
(
$values
,
$parents
);
Further reading and reference:
Examples—form arrays (besides the rest of the form section):
Basic Form Generation and Processing in Drupal 7
Here are the steps needed to generate and process a form in Drupal 7:
-
Choose an ID for your form, which is a string that should typically start with your module name. For example, you might choose
mymodule_personal_data_form
. -
In your mymodule.module file, define a form-generating function (also known as a form constructor) with the same name as the form ID, which returns the form array (see “Form Arrays, Form State Arrays, and Form State Objects”):
function
mymodule_personal_data_form
(
$form
,
&
$form_state
)
{
// Generate your form array here.
return
$form
;
}
-
If you need to perform any data validation steps on form submissions, create a form validation function called
mymodule_personal_data_form_validate()
. This function should callform_set_error()
if the submission is invalid, and it should do nothing if all is well. -
Create a form-submission function called
mymodule_personal_data_form_submit()
to process the form submissions (for instance, to save information to the database). Here is an example of a form submission function:
function
mymodule_personal_data_form_submit
(
&
$form
,
&
$form_state
)
{
// The values submitted by the user are in $form_state['values'].
$name
=
$form_state
[
'values'
][
'first_name'
];
// Values you stored in the form array are also available.
$info
=
$form_state
[
'values'
][
'information'
];
// Your processing code goes here, such as saving this to the database.
// Sanitize values before display, but not before storing to the database!
}
-
Call
drupal_get_form('mymodule_personal_data_form')
to build the form—do not call your form-generating function directly. Your validation and submission functions will be called automatically when a user submits the form. If your form is the sole content of a page whose path you are registering for in ahook_menu()
implementation, you can usedrupal_get_form()
as the page-generating function:
// Inside your hook_menu() implementation:
$items
[
'mymodule/my_form_page'
]
=
array
(
'page callback'
=>
'drupal_get_form'
,
'page arguments'
=>
array
(
'mymodule_personal_data_form'
),
// Don't forget the access information, title, etc.!
);
Further reading and reference:
-
hook_menu()
: “Registering for a URL in Drupal 7”
Examples—form processing (besides the rest of the form section):
-
Form-generating functions in Drupal core are listed in the “Form builder functions” topic on https://api.drupal.org.
-
The Form example in Examples for Developers.
Warning
Be careful about caching form output, because drupal_get_form()
adds
verification information to the form output, and this information is invalid
after some time has passed. If your form is displayed in a block, be sure that the
block is not cached; this is not a problem if the form is part of the main page
content.
Basic Form Generation and Processing in Drupal 8
In Drupal 8, to generate and process a form, create a form
class. By convention, form classes usually go into the
\Drupal\mymodule\Form
namespace, and of course you should pick a class name that
describes the form. You’ll also need to choose a unique ID for your form, which
should generally start with your module’s machine name.
Here is a simple example of a form class for a personal data form; this needs to go into the src/Form/PersonalDataForm.php file under your module directory:
namespace
Drupal\mymodule\Form
;
use
Drupal\Core\Form\FormBase
;
use
Drupal\Core\Form\FormStateInterface
;
class
PersonalDataForm
extends
FormBase
{
// getFormId() returns the form ID you chose, which must be unique.
public
function
getFormId
()
{
return
'mymodule_personal_data_form'
;
}
// buildForm() generates the form array.
public
function
buildForm
(
array
$form
,
FormStateInterface
$form_state
)
{
// Generate your form array here.
return
$form
;
}
// submitForm() processes the form submission.
public
function
submitForm
(
array
&
$form
,
FormStateInterface
$form_state
)
{
// Extract the values submitted by the user.
$values
=
$form_state
->
getValues
();
$name
=
$values
[
'first_name'
];
// Values you stored in the form array are also available.
$info
=
$values
[
'information'
];
// Your processing code goes here, such as saving this to the database.
// Sanitize values if you are displaying them, but do not sanitize before
// saving to the database!
}
}
Notes:
-
The three methods defined here are essential.
getFormID()
returns the unique ID you chose for your form.buildForm()
returns the form array; see “Form Arrays, Form State Arrays, and Form State Objects” for details.submitForm()
processes the form submission. -
You can also include a
validateForm()
method, if your form needs validation. If you find a validation error, call thesetErrorByName()
method on$form_state
; if there are no validation errors, just return. -
In a form class, in place of calling the
t()
function to translate user interface text, use thet()
method from theFormBase
class. -
If you are creating a form for simple (non-entity) configuration, you should extend
\Drupal\Core\Form\ConfigFormBase
instead of the genericFormBase
class.
Once you have a form class defined, you need to do one of the following to display your form:
-
If your form is the sole content of a page, you can put the class name into your mymodule.routing.yml file. For example, to make the personal data form appear on path mymodule/my_form_page, visible to anyone, you would use the following entry:
mymodule.personal_data_form: path: '/mymodule/my_form_page' defaults: _form: '\Drupal\mymodule\Form\PersonalDataForm' _title: 'Personal data form' requirements: _access: 'TRUE'
-
If your form is to appear in a block or inside some other content you are generating, you can add it to a render array as follows:
// Code without dependency injection or $container variable:
$my_render_array
[
'personal_data_form'
]
=
\Drupal
::
formBuilder
()
->
getForm
(
'Drupal\mymodule\Form\PersonalDataForm'
);
// With $container variable (dependency injection):
$builder
=
$container
->
get
(
'form_builder'
);
$my_render_array
[
'personal_data_form'
]
=
$builder
->
getForm
(
'Drupal\mymodule\Form\PersonalDataForm'
);
Further reading and reference:
Examples—form processing (besides the rest of the form section):
Creating Confirmation Forms
For security reasons, it is important to verify destructive actions connected with a URL. For instance, if your module has a URL that allows an administrative user to delete some data or a file, you should confirm this intention before deleting the data. One reason is that the user could have been tricked into visiting that URL by a cross-site scripting attack; also, sometimes people click links by mistake, and confirming before destroying all their data is the polite thing to do.
Drupal makes this type of confirmation easy. Here are the steps for Drupal 7:
-
Instead of registering your path with a function that performs the deletion directly, use
drupal_get_form()
as the page callback, passing in the name of a form-generating function. -
Have your form-generating function call
confirm_form()
to generate a confirmation form. -
Perform the data deletion in the form-submission function, which will only be called if the action is confirmed (which also means that the unique form token will be validated).
Here’s an example of the code:
// The menu router registration.
function
mymodule_menu
()
{
// ...
// Assume there is a content ID number.
$items
[
'admin/content/mycontent/delete/%'
]
=
array
(
'title'
=>
'Delete content item?'
,
'page callback'
=>
'drupal_get_form'
,
// Pass the content ID number to the form-generating function.
// It is position 4 in the path (starting from 0).
'page arguments'
=>
array
(
'mymodule_confirm_delete'
,
4
),
// Permission needs to be defined by the module.
'access arguments'
=>
array
(
'delete mycontent items'
),
);
// ...
}
// Form-generating function.
function
mymodule_confirm_delete
(
$form
,
$form_state
,
$id
)
{
// Save the ID for the submission function.
$form
[
'mycontent_id'
]
=
array
(
'#type'
=>
'value'
,
'#value'
=>
$id
,
);
return
confirm_form
(
$form
,
// You could load the item and display the title here.
t
(
'Are you sure you want to delete content item %id?'
,
array
(
'%id'
=>
$id
)),
// The URL path to return to if the user cancels.
'mymodule/mypath'
);
}
// Form-submission function.
function
mymodule_confirm_delete_submit
(
$form
,
$form_state
)
{
// Read the ID saved in the form.
$id
=
$form_state
[
'values'
][
'mycontent_id'
];
// Sanitize.
$id
=
(
int
)
$id
;
// Perform the data deletion.
// ...
// Redirect somewhere.
drupal_goto
(
'mymodule/my_form_page'
);
}
In Drupal 8, this same form can be generated by the following class, which needs to go into the src/Form/ConfirmDeleteForm.php file under the main module directory:
namespace
Drupal\mymodule\Form
;
use
Drupal\Core\Form\ConfirmFormBase
;
use
Drupal\Core\Form\FormStateInterface
;
use
Drupal\Core\Url
;
class
ConfirmDeleteForm
extends
ConfirmFormBase
{
protected
$to_delete_id
;
public
function
getFormId
()
{
return
'mymodule_confirm_delete'
;
}
// Note that when adding arguments to buildForm(), you need to give
// them default values, to avoid PHP errors.
public
function
buildForm
(
array
$form
,
FormStateInterface
$form_state
,
$id
=
''
)
{
// Sanitize and save the ID.
$id
=
(
int
)
$id
;
$this
->
to_delete_id
=
$id
;
$form
=
parent
::
buildForm
(
$form
,
$form_state
);
return
$form
;
}
public
function
getQuestion
()
{
return
$this
->
t
(
'Are you sure you want to delete content item %id?'
,
array
(
'%id'
=>
$this
->
to_delete_id
));
}
public
function
getCancelUrl
()
{
return
new
Url
(
'mymodule.mydescription'
);
}
public
function
submitForm
(
array
&
$form
,
FormStateInterface
$form_state
)
{
$id
=
$this
->
to_delete_id
;
// Perform the data deletion.
// ...
// Redirect somewhere.
$form_state
->
setRedirect
(
'mymodule.personal_data_form'
);
}
}
You’ll also need to display this form on a page. Here’s a
mymodule.routing.yml entry, with a placeholder corresponding to the
form builder input variable name $id
:
mymodule.delete_confirm: path: '/admin/content/mycontent/delete/{id}' defaults: _form: '\Drupal\mymodule\Form\ConfirmDeleteForm' _title: 'Delete content item?' requirements: _permission: 'delete mycontent items'
Further reading and reference:
Adding Autocomplete to Forms
If you have a form where a user needs to select a value from several choices, there are several ways to do it:
-
If there are fewer than about seven choices, you should use checkboxes if the user can select multiple items, and radio buttons if the user can only select one item.
-
If there are more than seven choices, you should use a select list.
-
However, if you have a very large number of choices, select lists do not perform well in browsers; in this case, you should use an autocomplete text field: choices pop up as the user begins to enter text.
-
You may also want to use an autocomplete text field if the user is free to make an entry that was not one of the suggested choices. This will let users see existing choices, prompting them to select one if it exists, while still letting them create a new entry.
To make a text input field have autocomplete behavior in Drupal, here are the steps:
-
In Drupal 7, add an
'#autocomplete_path'
property to your'textfield'
form element array, with a URL path in it. In Drupal 8, the property is'#autocomplete_route_name'
, and the value is a route machine name. Or to autocomplete on an entity in Drupal 8, add an'entity_autocomplete'
render element to your array. This looks like:
// In a form-generating function:
$form
[
'my_autocomplete_field'
]
=
array
(
'#type'
=>
'textfield'
,
'#title'
=>
t
(
'Autocomplete field'
),
// Drupal 7:
'#autocomplete_path'
=>
'mymodule/autocomplete'
,
// Drupal 8:
'#autocomplete_route_name'
=>
'mymodule.autocomplete'
,
);
// For an entity autocomplete in Drupal 8 for Nodes:
$form
[
'my_node_element'
]
=
array
(
'#type'
=>
'entity_autocomplete'
,
'#target_type'
=>
'node'
,
// Limit this to just Articles.
'#selection_settings'
=>
array
(
'target_bundles'
=>
array
(
'article'
),
),
);
-
Register for this URL path in your
hook_menu()
implementation (Drupal 7) or for the route in your mymodule.routing.yml file (Drupal 8). This looks like:
// Inside Drupal 7 hook_menu() implementation:
$items
[
'mymodule/autocomplete'
]
=
array
(
'page callback'
=>
'mymodule_autocomplete'
,
'access arguments'
=>
array
(
'use company field'
),
'type'
=>
MENU_CALLBACK
,
);
# Inside Drupal 8 mymodule.routing.yml file:
mymodule
.
autocomplete
:
path
:
'/mymodule/autocomplete'
defaults
:
_controller
:
'\Drupal\mymodule\Controller\MyUrlController::autocomplete'
requirements
:
_permission
:
'use company field'
-
Define the page-callback function or page-generation method. In Drupal 7, it will take one argument (the string the user has typed); in Drupal 8, the argument will be the Symfony request. In either case, it should return an array of responses in JSON format, as in these examples:
// Drupal 7:
function
mymodule_autocomplete
(
$string
=
''
)
{
$matches
=
array
();
if
(
$string
)
{
// Sanitize $string and find appropriate matches -- about 10 or fewer.
// Put them into $matches as $key => $visible text.
// ...
}
drupal_json_output
(
$matches
);
}
// Drupal 8, top of MyUrlController class file:
use
Symfony\Component\HttpFoundation\JsonResponse
;
use
Symfony\Component\HttpFoundation\Request
;
// Drupal 8, new method in MyUrlController class:
public
function
autocomplete
(
Request
$request
)
{
$string
=
$request
->
query
->
get
(
'q'
);
$matches
=
array
();
if
(
$string
)
{
// Sanitize $string and find appropriate matches -- about 10 or fewer.
// Put them into $matches as items, each an array with
// 'value' and 'label' elements.
// ...
}
return
new
JsonResponse
(
$matches
);
}
Further reading and reference:
-
Autocomplete is a special case of Ajax (see “Programming with Ajax in Drupal”). Autocomplete is covered here because in Drupal it is handled in a completely different manner from other Ajax responses.
Examples—autocomplete:
-
There are several examples of autocompletes in Drupal core version 7, such as the author name field in
node_form()
, which auto-completes on user names at path user/autocomplete. This path is registered inuser_menu()
, and its page-callback function isuser_autocomplete()
. -
In Drupal 8, most ad-hoc autocomplete functionality has been removed in favor of the generic entity autocomplete described in the preceding text. But the Block module still has a special autocomplete controller for block categories. The routing is in core/modules/block/block.routing.yml (route name
block.category_autocomplete
) with the\Drupal\block\Controller\CategoryAutocompleteController::autocomplete()
page-controller method. -
There is a complete standalone autocomplete example in the Ajax example in Examples for Developers.
Altering Forms
One of the more common reasons that someone building a Drupal site would decide to create a custom module is to alter a form that is displayed by Drupal core or another module. Typically, the reason is that the site owner or site designer decides that some of the text on the form is confusing, wants some part of the form hidden, wants to change the order of fields on a form, or wants some additional validation to be done on form submissions. All of these alterations can be done by implementing a form alter hook.
Before deciding you need a custom form-altering module, however, you should check to see if you can alter the form in a different way. Some core and contributed modules, for example, have configuration options that will let you alter labels on forms, and you can also use the String Overrides contributed module to make global text changes (such as changing all Submit buttons to say Send). If you want to add text at the top of a form, you might be able to use a block. Also, content-editing forms are configurable in the administrative user interface: you can add help text to fields, change field labels, change the order of fields, add and remove fields from content types, and change the displayed name of the content type, among other settings. In Drupal 8, you can also define form modes, which allow you to make different content editing forms for different situations, in the user interface, with different subsets of fields displayed, different labels, and different ordering. Each content type also has several settings for comments that affect the comment form, and there are many other examples of configuration options—so be sure to investigate before you start programming.
If you do need to alter a form via an alter hook in your custom module, here are the steps (in Drupal 7 or 8):
-
Figure out the form ID of the form you are altering. The easiest way to do this is to look at the HTML source of the page with the form—the ID will be the
id
attribute of the HTMLform
tag. For this example, let’s assume the ID isthe_form_id
. -
Implement
hook_form_FORM_ID_alter()
by declaring a function calledmymodule_form_the_form_id_alter()
in your mymodule.module file. Some forms, like field widget forms, use a different alter hook, such ashook_field_widget_form_alter()
; these hooks work the same way ashook_form_FORM_ID_alter()
. -
Alter the form array in this function.
Note
Implementing hook_form_FORM_ID_alter()
can sometimes lead to some
crazy-looking function names. For instance, in both Drupal 7 and 8, there is a
module used for testing called form_test.module. This module defines a form
whose ID is form_test_alter_form
, and then it implements
hook_form_FORM_ID_alter()
to test
form alteration hook functionality. The name of the
implementing function is therefore composed of the module’s machine name, then
form
, then the form ID, then alter
, resulting in
form_test_form_form_test_alter_form_alter()
—kind of a mouthful!
As an example, assume that you want to change the user-registration form on a
site so that it only allows people to register using email addresses within your
company’s domain. The form ID in this case is user_register_form
, and here
is the alter function you would need to define:
// Form alteration in Drupal 7. Drupal 8 is the same except the
// function signature, see below.
function
mymodule_form_user_register_form_alter
(
&
$form
,
&
$form_state
,
$form_id
)
{
// Change the label on the email address field.
$form
[
'account'
][
'mail'
][
'#title'
]
=
t
(
'Company e-mail address'
);
// Add a validation function.
$form
[
'#validate'
][]
=
'mymodule_validate_register_email'
;
}
// Drupal 8 use statement needed:
use
Drupal\Core\Form\FormStateInterface
;
// Drupal 8 function signature:
function
mymodule_form_user_register_form_alter
(
&
$form
,
FormStateInterface
$form_state
,
$form_id
)
// Validation function in Drupal 7.
function
mymodule_validate_register_email
(
$form
,
$form_state
)
{
=
$form_state
[
'values'
][
'mail'
];
// Check that the email is within the company domain.
// If not, call form_set_error('mail', t('message goes here'));
}
// Validation function in Drupal 8.
function
mymodule_validate_register_email
(
$form
,
FormStateInterface
$form_state
)
{
$values
=
$form_state
->
getValues
();
=
$values
[
'mail'
];
// Check that the email is within the company domain.
// If not, call $form_state->setErrorByName('mail', t('message goes here'));
}
Programming with Ajax in Drupal
Ajax is a technique whereby a web page can make an asynchronous (background) request to a URL without a full page load, in response to a JavaScript event (mouse click, typing, etc.) on an HTML element. When the request finishes, JavaScript commands are run, typically updating part of the page. Autocomplete text fields in forms, covered in “Adding Autocomplete to Forms”, are one example of Ajax handling in Drupal; generic Ajax responses in forms work differently and are covered in this section.
Note
Ajax was formerly known as AJAX, or Asynchronous JavaScript and XML. However, these days few people actually work with XML when doing their asynchronous requests, so what was once the acronym AJAX has become Ajax, and it is used to mean any kind of asynchronous JavaScript request (whether or not XML is involved).
Like many aspects of Drupal, because Drupal is written in PHP and browsers support JavaScript, you can technically use any Ajax programming techniques you know to accomplish your Ajax requirements. However, you should use the Drupal Ajax framework instead, which provides several benefits:
-
Defining the Ajax responses for HTML elements is done in the form array, as part of the Drupal Form API, which is much easier than doing it from scratch.
-
Drupal performs its standard security checks for forms and HTTP requests.
-
In place of completely handling the server end (the URL request), you can let Drupal use its standard Ajax URL and just provide the PHP function to be called to handle the processing. You can also use your own URL if you wish; in that case, you would use the standard Drupal routing system to set up your Ajax URL.
-
In place of writing all the JavaScript commands for the browser when the response is returned, the Drupal Ajax framework provides an easy and flexible way to define the browser actions.
The remainder of this section describes how to use the Drupal Ajax framework.
Setting Up a Form for Ajax
Drupal Ajax responses are specified in forms. To set up an Ajax response in a form,
the main thing you need to do is add the #ajax
property (an array) to the form
element that should trigger the response. In the #ajax
array, provide values
for one or more of the following keys:
-
callback
: The name of the PHP function or method to call when the event occurs. -
In place of
callback
, if you want to use your own URL, in Drupal 7 you can instead define a value for thepath
key (a URL path string), and register for the URL in the standard Drupal way. In Drupal 8, you’d use theurl
key and its value would be a\Drupal\Core\Url
object. -
wrapper
: If you want the browser response to replace part of the page markup on return, set up a<div>
with anid
attribute, and provide this ID. By default, the entire<div>
will be completely replaced with returned content (including the<div>
tags), but you can also supply a value formethod
, equal to'append'
,'prepend'
,'before'
,'after'
, or the name of another JQuery manipulation function to change this. Omitwrapper
if your response is more complex than just replacing markup. -
event
: Form elements have default events to respond to, but you can override the default by specifying the name of a JavaScript event. -
prevent
: Optionally, specify an event to prevent from being triggered (e.g., if you respond to'mousedown'
events, you might want to prevent'click'
events from being triggered when the mouse comes back up). -
There are additional Ajax elements governing effect speeds, progress indicators, and other factors of the Ajax response.
Warning
If you are using your own URL for Ajax, via the path
(Drupal 7) or
url
(Drupal 8) element in your #ajax
property instead of callback
, you’ll
need to construct your URL so that all the information you need is part of the
HTTP request (generally, you’ll put the information into URL query parameters);
it is usually simpler to use callback
.
As the start of an example (continued in the rest of this section), here’s a
form array with two Ajax-triggering elements, and two HTML <div>
elements to
put responses in:
$form
[
'ajax_output_1'
]
=
array
(
'#type'
=>
'markup'
,
'#markup'
=>
'<div id="ajax-output-spot"></div>'
,
);
$form
[
'text_trigger'
]
=
array
(
'#type'
=>
'textfield'
,
'#title'
=>
t
(
'Type here to trigger Ajax'
),
'#ajax'
=>
array
(
'event'
=>
'keyup'
,
'wrapper'
=>
'ajax-output-spot'
,
'callback'
=>
'mymodule_ajax_text_callback'
,
),
);
$form
[
'ajax_output_2'
]
=
array
(
'#type'
=>
'markup'
,
'#markup'
=>
'<div id="other-ajax-response-spot"></div>'
,
);
$form
[
'button_trigger'
]
=
array
(
'#type'
=>
'button'
,
'#value'
=>
t
(
'Click here to trigger Ajax'
),
'#ajax'
=>
array
(
'callback'
=>
'mymodule_ajax_button_callback'
,
),
);
If you are doing this in Drupal 8, the callbacks could also be public static class methods on the form class, such as:
'callback'
=>
'Drupal\mymodule\Form\PersonalDataForm::ajaxTextCallback'
,
'callback'
=>
'Drupal\mymodule\Form\PersonalDataForm::ajaxButtonCallback'
,
The Ajax callback specified in the callback
element of the #ajax
property
on a form element is a PHP function that receives $form
and $form_state
as
arguments. $form_state['triggering_element']
(Drupal 7) or
$form_state->get('triggering_element')
(Drupal 8) will tell you which form
element triggered the Ajax response, so you can use the same callback to handle
Ajax responses to several different form elements, if you wish. You can also
retrieve all of the currently entered form values in $form_state
as you would
in any other form processing. The following sections give a few examples of
these callbacks.
In most cases, the other thing you need to do for Ajax to work properly is to
make sure that the form is rebuilt properly during Ajax handling. To do that,
you’ll need to set $form_state['rebuild']
to TRUE
in your form submit
handler function (Drupal 7), or call the setRebuild()
method on the form
state object in Drupal 8.
Further reading and reference:
-
JQuery manipulation functions: http://bit.ly/jquery_manipulation
-
Online documentation for Ajax in Drupal 7 (also mostly applies to Drupal 8): http://bit.ly/js_api_in_drupal7
Examples—Ajax (besides the rest of this section):
-
The Ajax example in Examples for Developers
Wrapper-Based Ajax Callback Functions
If you use the wrapper
element of the #ajax
property, your callback
returns the HTML markup to replace the wrapper <div>
, or (preferably) a
render array that generates the desired HTML markup. In addition, any calls to
drupal_set_message()
during processing will result in messages being prepended
to the returned markup. Here’s an example callback function for Drupal 7:
function
mymodule_ajax_text_callback
(
$form
,
&
$form_state
)
{
// Read the text from the text field.
$text
=
$form_state
[
'values'
][
'text_trigger'
];
if
(
!
$text
)
{
$text
=
t
(
'nothing'
);
}
// Set a message.
drupal_set_message
(
t
(
'You have triggered Ajax'
));
// Return a render array for markup to replace the wrapper <div> contents.
return
array
(
'#type'
=>
'markup'
,
// Text was not sanitized, so use @variable in t() to sanitize.
// Be sure to include the wrapper div!
'#markup'
=>
'<div id="ajax-output-spot">'
.
t
(
'You typed @text'
,
array
(
'@text'
=>
$text
))
.
'</div>'
,
);
}
Only the first few lines are different in Drupal 8, to make it a
static method on the PersonalDataForm
class defined in “Basic Form Generation and Processing in Drupal 8” and
use the Drupal 8 $form_state
interface:
public
static
function
ajaxTextCallback
(
array
$form
,
FormStateInterface
$form_state
)
{
// Read the text from the text field.
$text
=
$form_state
->
getValues
()[
'text_trigger'
];
Further reading and reference:
Command-Based Ajax Callback Functions in Drupal 7
If you are not using a wrapper
element in your #ajax
property, your
Drupal 7 Ajax callback function should return a set of Ajax commands from
the Drupal Ajax framework. Note that unlike when using wrapper
, if you
are using a command-based callback,
drupal_set_message()
does not automatically trigger messages to be displayed.
Here’s an example of a callback using commands for Drupal 7:
function
mymodule_ajax_button_callback
(
$form
,
&
$form_state
)
{
$commands
=
array
();
// Replace HTML markup inside the div via a selector.
$text
=
t
(
'The button has been clicked'
);
$commands
[]
=
ajax_command_html
(
'div#other-ajax-spot'
,
$text
);
// Add some CSS to the div.
$css
=
array
(
'background-color'
=>
'#ddffdd'
,
'color'
=>
'#000000'
);
$commands
[]
=
ajax_command_css
(
'div#other-ajax-spot'
,
$css
);
return
array
(
'#type'
=>
'ajax'
,
'#commands'
=>
$commands
);
}
Each command in the returned array is the return value of one of the Drupal Ajax command functions. You can find all of these functions listed in the “Ajax framework commands” topic on https://api.drupal.org.
Command-Based Ajax Callback Functions in Drupal 8
If you are not using a wrapper
element in your #ajax
property, your
Drupal 8 Ajax callback function should return a set of Ajax commands from the
Drupal Ajax framework, in the form of an object of class
\Drupal\Core\Ajax\AjaxResponse
. Note that unlike when using wrapper
, if you
are using a command-based callback,
drupal_set_message()
does not automatically trigger messages to be displayed.
Here is an example of a callback using commands, as a method on the
PersonalDataForm
class defined in “Basic Form Generation and Processing in Drupal 8”:
use
Drupal\Core\Ajax\AjaxResponse
;
use
Drupal\Core\Ajax\HtmlCommand
;
use
Drupal\Core\Ajax\CssCommand
;
public
static
function
ajaxButtonCallback
(
array
$form
,
FormStateInterface
$form_state
)
{
$response
=
new
AjaxResponse
();
// Replace HTML markup inside the div via a selector.
$text
=
t
(
'The button has been clicked'
);
$response
->
addCommand
(
new
HtmlCommand
(
'div#other-ajax-spot'
,
$text
));
// Add some CSS to the div.
$css
=
array
(
'background-color'
=>
'#ddffdd'
,
'color'
=>
'#000000'
);
$response
->
addCommand
(
new
CssCommand
(
'div#other-ajax-spot'
,
$css
));
return
$response
;
}
Each command you add to the AjaxResponse
object via the
AjaxResponse::addCommand()
method is an object that
implements \Drupal\Core\Ajax\CommandInterface
. The available commands can be
found in the core/lib/Drupal/Core/Ajax directory in the Drupal source code
(find classes there whose names end in Command
).
Programming with Entities and Fields
This section covers programming with Drupal entities and fields. The sections on defining entity types, field types, widgets, and formatters are independent of one another, so skim the terminology section first, and then you can skip to the section you need. The code samples in this section complement, but do not duplicate, the well-documented Entity and Field examples in the Examples for Developers project.
Further reading and reference:
Examples—entity and field programming (besides the rest of this section):
-
The Drupal core entities and fields are all good to look at; Drupal 8 has even more Drupal core examples than Drupal 7.
-
Entity example in Examples for Developers. Note that the Drupal 7 example is a bit different from the example in this section, because it does not make use of the contributed Entity API module.
-
Field example in Examples for Developers.
-
The Node example in Examples for Developers shows how to create a content type for the core Node entity in a module.
Warning
There is sometimes confusion between entity fields and database table fields. Within this section, the term field will always mean an entity field as defined in this section, and any references to database table fields will be clearly noted as such.
Terminology of Entities and Fields
The Drupal entity and field systems introduced quite a bit of terminology that you’ll need to be familiar with if you’re planning on doing any entity or field programming. Here’s a list of terms and other background information:
- Entity
-
As of Drupal version 7, Drupal core defines the concept of an entity, which stores data (such as content or settings) for a Drupal website.
- Entity type
-
Each entity type represents a particular kind of data, and comes with a small number of properties, such as an ID, a universal identifier (UUID), and a label or title.
- Content and configuration entities
-
Drupal 8 formally divides entities into content entities (for content that should be displayed to site visitors) and configuration entities (for site configuration). This distinction is not explicit in Drupal 7, and entities in Drupal 7 are really meant only for content.
- Drupal core entity types
-
Drupal core version 7 defines five main user-visible entity types: node (for basic content), taxonomy_term and taxonomy_vocabulary (for classification of content), comment (for comments attached to nodes), and user (for user account information). Drupal 7 core also defines the file entity type, which is used internally to manage uploaded files. Drupal 8 core defines many additional entity types, most of which are configuration entities. Also, in Drupal 8, the comment entity type is generalized: comments can be attached to any entity type, not just nodes.
- Module-defined entity types
-
The Drupal API allows modules to define additional entity types. The API is quite different for Drupal 7 and Drupal 8 and is described in the following sections.
- Bundle
-
Each content entity type can have one or more bundles, which are groupings of the entity objects within a given entity type. For instance, the bundles of the node entity type are content types, which an administrator can define within the Drupal user interface (modules can define them too); examples of content types are basic pages, news items, blog posts, and forum posts. The bundles of the taxonomy entity type are vocabularies, and the objects are the individual taxonomy terms. The user entity type doesn’t use bundles, and its objects are user accounts. Each entity object belongs to exactly one bundle (assuming that you count entities that don’t use bundles as having all objects belonging to the same, default bundle).
- Fields, base fields, and properties
-
Most content entity types are fieldable, meaning that fields can be added to each bundle of the entity type (the fields can be different for each bundle within an entity type). In Drupal 7, fields supplement the intrinsic properties of the entity type; in Drupal 8, the entity properties are actually fields themselves (they’re known as base fields in Drupal 8). Fields and intrinsic properties both store information, which could be text, numbers, attached files, images, media URLs, or other data, and fields can be single- or multiple-valued. Some entity types are not fieldable or do not allow administrators to change their fields; for example, configuration entity types are normally not fieldable.
- Field type
-
Each field has a field type, which defines what type of data the field stores; Drupal core defines several field types—including one-line text, formatted long text, and images—and modules can define additional field types. A field type can be used to create one or more individual fields, which have machine names and other settings pertaining to data storage (such as how many values they can store); the settings cannot be changed after the field is created.
- Field instance
-
Once a field is created, it can be attached to one or more bundles; each field/bundle combination is known as a field instance. In Drupal 7, fields can be shared across entity types; in Drupal 8, fields are specific to each entity type, so for instance, if you needed a first-name field for both Nodes and Comments, you’d have to create it twice. Field instances have settings such as whether the field is required and a label; these settings can be different for each bundle and can be changed later.
- Widgets and form modes
-
When a user is creating or editing an entity object, a field widget is used to edit the field data on the entity editing form. For instance, a field storing text can use a normal HTML text field as its widget, or if its values are restricted to a small set, it could use an HTML select, radio buttons, or checkboxes. Drupal core defines the common widgets needed to edit its fields in standard ways, and modules can define widgets for their fields or other modules’ fields. In Drupal 7, widgets are assigned to each field instance when the field is attached to the bundle. In Drupal 8, each bundle can have one or more form modes, which allow entity objects and their fields to be edited differently under different circumstances. In each form mode, some fields can be hidden, display order can be chosen, and a widget can be chosen for each visible field.
- Formatters and view modes
-
When an entity object is being displayed, field formatters are used to display the field data. For instance, a long text field could be formatted as plain text (with all HTML tags stripped out), passed through a text filter, or truncated to a particular length. Modules can define field formatters for their own or other modules’ field types. Each bundle can have one or more view modes (such as full page and teaser for the node entity type); fields can be hidden or shown, ordered, and assigned different formatters in each view mode. View modes allow entity objects and their fields to be displayed differently under different circumstances; this is mostly useful for fieldable content entity types.
- Translations and revisions
-
The data in entity objects and their fields can be edited and translated, and many entity types keep track of revisions, making it possible to revert entity and field data to a prior version.
Tip
If your data storage needs are similar to an existing entity type, it is a good idea to use it instead of defining your own entity type. This will be a lot less work, because existing entity types include administrative screens and other functionality, and it will also allow you to use the many add-on modules that work with existing entity types.
Defining your own entity type is a good idea for these circumstances:
-
In Drupal 7, to store groups of settings for a module, to allow them to be translated with the Entity Translation module.
-
In Drupal 8, to store configuration that has multiple copies, as described in “Defining a Configuration Entity Type in Drupal 8”.
-
In either Drupal 7 or 8, to define storage for a set of content for a site that needs a completely different permissions system and display mechanism from the Drupal core node entity type (and from other existing entity types). Define your own entity when the additional programming that would be needed to coerce an existing entity type into doing what you want would be more work than the programming needed to define a separate entity type.
Defining an Entity Type in Drupal 7
This section shows how to define a new entity type in Drupal 7, which could be used to store a special type of content, or for module settings. You might want to download the Entity example from Examples for Developers and follow along there, or perhaps look at the code for one of the Drupal core entities.
Step 1: Implement hook_entity_info()
The first step in defining a new entity type is to implement
hook_entity_info()
in your module. In Drupal 7, it is
advisable to make use of the contributed Entity API module, as it takes care
of many standard operations for you;
you may also want to make use of the Entity Construction Kit
module. To use the Entity API module, you’ll need your module to have a
dependency in its mymodule.info file:
dependencies[] = entity
With that taken care of, to define an entity type whose machine name is
myentity
, declare the following function in your mymodule.module file:
// Simple internal-use entity.
function
mymodule_entity_info
()
{
$return
=
array
();
$return
[
'myentity'
]
=
array
(
// Define basic information.
'label'
=>
t
(
'Settings for My Module'
),
'plural label'
=>
t
(
'Settings for My Module'
),
'fieldable'
=>
TRUE
,
// Provide information about the database table.
'base table'
=>
'mymodule_myentity'
,
'entity keys'
=>
array
(
'id'
=>
'myentity_id'
,
'label'
=>
'title'
,
),
// Use classes from the Entity API module.
'entity class'
=>
'Entity'
,
'controller class'
=>
'EntityAPIController'
,
// Have Entity API set up an administrative UI.
'admin ui'
=>
array
(
'path'
=>
'admin/myentity'
,
),
'module'
=>
'mymodule'
,
'access callback'
=>
'mymodule_myentity_access'
,
// For content-type entities only, define the callback that
// returns the URL for the entity.
'uri callback'
=>
'mymodule_myentity_uri'
,
);
return
$return
;
}
// For content-type entities, return the URI for an entity.
function
mymodule_myentity_uri
(
$entity
)
{
return
array
(
'path'
=>
'myentity/'
.
$entity
->
myentity_id
,
);
}
Note about the URI callback: if your entity is for settings, you probably
just need a way to edit the settings, rather than a dedicated page to display
them. So, you probably do not need a URL for each entity (akin to node/1 for a
node entity). In this case, you can leave out the URI callback and the
hook_menu()
entry defined in a later step.
Further reading and reference:
-
Entity API module: https://www.drupal.org/project/entity
-
Entity Construction Kit module: https://www.drupal.org/project/eck
Step 2: Implement hook_schema()
The next step, for both settings and content entity types, is to implement
hook_schema()
in your mymodule.install file, to set up the database table
for storing your entity information. The table name
and some of the database field names need to match what you put into your
hook_entity_info()
implementation, and you’ll also want a database field for
language (assuming that you want your entity objects to be translatable), and
possibly additional database fields to keep track of when entity objects are
created and last updated. Here’s the schema for the settings entity type
example:
function
mymodule_schema
()
{
$schema
=
array
();
$schema
[
'mymodule_myentity'
]
=
array
(
'description'
=>
'Storage for myentity entity: settings for mymodule'
,
'fields'
=>
array
(
'myentity_id'
=>
array
(
'description'
=>
'Primary key: settings ID.'
,
'type'
=>
'serial'
,
'unsigned'
=>
TRUE
,
'not null'
=>
TRUE
,
),
'title'
=>
array
(
'description'
=>
'Label assigned to this set of settings'
,
'type'
=>
'varchar'
,
'length'
=>
200
,
'default'
=>
''
,
),
'language'
=>
array
(
'description'
=>
'Language of this set of settings'
,
'type'
=>
'varchar'
,
'length'
=>
12
,
'not null'
=>
TRUE
,
'default'
=>
''
,
),
// Consider adding additional fields for time created, time updated.
),
'primary key'
=>
array
(
'myentity_id'
),
'indexes'
=>
array
(
'language'
=>
array
(
'language'
),
// Add indexes for created/updated here too.
),
);
return
$schema
;
}
Further reading and reference:
Step 3: Add predefined fields in hook_install()
If you are defining an entity type to use for settings, the next step is to
attach fields to your entity bundle to store the settings you need. For a
content-type entity, you may want to just let administrators add the fields in
the administrative user interface (the Entity API module provides the URLs and
screens), in which case you can skip this step. To add fields programmatically,
implement hook_install()
in your mymodule.install file, using Drupal core
Field API functions:
function
mymodule_install
()
{
// Create a plain text field for a setting.
$field
=
field_create_field
(
array
(
'field_name'
=>
'myentity_setting_1'
,
'type'
=>
'text'
,
'entity_types'
=>
array
(
'myentity'
),
'locked'
=>
TRUE
,
'translatable'
=>
TRUE
,
));
// Attach the field to the entity bundle.
$instance
=
field_create_instance
(
array
(
'field_name'
=>
'myentity_setting_1'
,
'entity_type'
=>
'myentity'
,
'bundle'
=>
'myentity'
,
'label'
=>
t
(
'Setting 1'
),
'description'
=>
t
(
'Help for this setting'
),
'required'
=>
TRUE
,
'widget'
=>
array
(
'type'
=>
'text_textfield'
,
),
'display'
=>
array
(
'default'
=>
array
(
'label'
=>
'above'
,
'type'
=>
'text_default'
,
),
),
));
// Repeat these two function calls for each additional field.
}
Further reading and reference:
Step 4: Set up display
The next step is to set up your entity type so that its objects can be displayed,
which is only necessary for a content-type entity. Given the mymodule_myentity_uri()
URL callback function that
was declared in “Step 1: Implement hook_entity_info()”, you need to register for the URL it returns and tell Drupal to use the Entity API
module’s entity_view()
function to display the entity:
function
mymodule_menu
()
{
$items
=
array
();
// Register for the URL that mymodule_myentity_uri() returns.
// The placeholder %entity_object in the URL is handled by the Entity
// API function entity_object_load().
$items
[
'myentity/%entity_object'
]
=
array
(
// entity_object_load() needs to know what the entity type is.
'load arguments'
=>
array
(
'myentity'
),
// Use a callback for the page title, not a static title.
'title callback'
=>
'mymodule_myentity_page_title'
,
'title arguments'
=>
array
(
1
),
// Callback to display the entity.
'page callback'
=>
'entity_ui_entity_page_view'
,
'page arguments'
=>
array
(
1
),
// Access callback.
'access callback'
=>
'mymodule_myentity_access'
,
'access arguments'
=>
array
(
'view'
,
array
(
1
)),
);
return
$items
;
}
// Title callback function registered above.
function
mymodule_myentity_page_title
(
$entity
)
{
return
$entity
->
title
;
}
Further reading and reference:
Step 5: Set up editing and management
Both settings and content entity types need management pages and forms for
creating and editing entity objects. The Entity API module sets these
up for you using the information that you provided in your hook_entity_info()
implementation (in “Step 1: Implement hook_entity_info()”). There are several functions that you do need to
define, however:
-
An access callback (which defines access permissions for your entity type). The function name is provided in your
hook_entity_info()
andhook_menu()
implementations. You’ll also need to implementhook_permission()
to define permissions. -
A function to generate the entity object editing form, which must be called
myentity_form()
. A corresponding form-submission handler is also needed. Your form needs to handle editing the title and the language, and then it needs to callfield_attach_form()
to let the Field module add the other fields to the form.
Here is the code for these functions:
// Define the permissions.
function
mymodule_permission
()
{
return
array
(
'view myentity'
=>
array
(
'title'
=>
t
(
'View my entity content'
),
),
'administer myentity'
=>
array
(
'title'
=>
t
(
'Administer my entities'
),
),
);
}
// Access callback for Entity API.
function
mymodule_myentity_access
(
$op
,
$entity
,
$account
=
NULL
)
{
// $op is 'view', 'update', 'create', etc.
// $entity could be NULL (to check access for all entity objects)
// or it could be a single entity object.
// $account is either NULL or a user object.
// In this simple example, just check permissions for
// viewing or administering the entity type generically.
if
(
$op
==
'view'
)
{
return
user_access
(
'view myentity'
,
$account
);
}
return
user_access
(
'administer myentity'
,
$account
);
}
// Form-generating function for the editing form.
function
myentity_form
(
$form
,
$form_state
,
$entity
)
{
$form
[
'title'
]
=
array
(
'#title'
=>
t
(
'Title'
),
'#type'
=>
'textfield'
,
'#default_value'
=>
isset
(
$entity
->
title
)
?
$entity
->
title
:
''
,
);
// Build language options list.
$default
=
language_default
();
$options
=
array
(
$default
->
language
=>
$default
->
name
);
if
(
module_exists
(
'locale'
))
{
$options
=
array
(
LANGUAGE_NONE
=>
t
(
'All languages'
))
+
locale_language_list
(
'name'
);
}
// Add language selector or value to the form.
$langcode
=
isset
(
$entity
->
language
)
?
$entity
->
language
:
''
;
if
(
count
(
$options
)
>
1
)
{
$form
[
'language'
]
=
array
(
'#type'
=>
'select'
,
'#title'
=>
t
(
'Language'
),
'#options'
=>
$options
,
'#default_value'
=>
$langcode
,
);
}
else
{
$form
[
'language'
]
=
array
(
'#type'
=>
'value'
,
'#value'
=>
$langcode
,
);
}
$form
[
'actions'
]
=
array
(
'#type'
=>
'actions'
);
$form
[
'actions'
][
'submit'
]
=
array
(
'#type'
=>
'submit'
,
'#value'
=>
t
(
'Save'
),
'#weight'
=>
999
,
);
field_attach_form
(
'myentity'
,
$entity
,
$form
,
$form_state
,
$langcode
);
return
$form
;
}
// Form submission handler for editing form.
function
myentity_form_submit
(
$form
,
&
$form_state
)
{
// Make use of Entity API class.
$entity
=
entity_ui_form_submit_build_entity
(
$form
,
$form_state
);
$entity
->
save
();
// Redirect to the management page.
$form_state
[
'redirect'
]
=
'admin/myentity'
;
}
Further reading and reference:
Step 6: Enable your module
If you have followed all of these steps, you should be able to enable your
module and see your entity type’s administration pages at
example.com/admin/myentity (as given in the hook_entity_info()
implementation). If you had previously installed your module, you’ll probably
need to uninstall (losing all your data) and then reenable. You may be able to
just disable and enable, if you also define an update hook that adds the
necessary database tables.
Defining a Content Entity Type in Drupal 8
Entity types in Drupal 8 are a type of plugin, so they use the plugin system described in “Implementing a plugin in a module”. This section describes how to define a content entity type, and the following section (“Defining a Configuration Entity Type in Drupal 8”) describes how to define a configuration entity type.
Before you start, you will need to choose a machine name for your entity type,
which should be short but unique; in this example, myentity
is the machine
name. The maximum length for an entity type machine name is 32 characters.
Step 1: Define the entity interface and class
The first steps in defining an entity in Drupal 8 are to define an entity
interface and entity class. The interface should define the get*
and set*
methods for the base properties of your entity (such as getTitle()
and
setTitle()
for the title property), and should
extend \Drupal\Core\Entity\ContentEntityInterface
. The class should implement
your interface and normally extends
\Drupal\Core\Entity\ContentEntityBase
; it will need to implement any methods
you put on the interface, plus ContentEntityInterface::baseFieldDefinitions()
.
The interface should either go in the
top-level namespace of your module, or in the Entity
namespace underneath; the
entity class needs to be in the Entity
namespace and needs to have
\Drupal\Core\Entity\Annotation\ContentEntityType
annotation in its
documentation header to be recognized as an entity type definition plugin.
The interface can usually be pretty simple, because normally there are only a few base properties (most of the data goes in fields). Here is an example (minus documentation headers), which goes in src/Entity/MyEntityInterface.php under the module directory:
namespace
Drupal\mymodule\Entity
;
use
Drupal\Core\Entity\ContentEntityInterface
;
interface
MyEntityInterface
extends
ContentEntityInterface
{
public
function
getTitle
();
public
function
setTitle
(
$title
);
}
The class is a bit more complicated, because of the necessary annotation in the documentation header. Here is a fairly minimal example, which goes in src/Entity/MyEntity.php under the module directory:
namespace
Drupal\mymodule\Entity
;
use
Drupal\mymodule\Entity\MyEntityInterface
;
use
Drupal\Core\Entity\ContentEntityBase
;
use
Drupal\Core\Entity\EntityTypeInterface
;
use
Drupal\Core\Field\BaseFieldDefinition
;
/**
* Represents a MyEntity entity object.
*
* @ContentEntityType(
* id = "myentity",
* label = @Translation("My entity"),
* bundle_label = @Translation("My entity subtype"),
* fieldable = TRUE,
* handlers = {
* "view_builder" = "Drupal\Core\Entity\EntityViewBuilder",
* "list_builder" = "Drupal\Core\Entity\EntityListBuilder",
* "views_data" = "Drupal\mymodule\Entity\MyEntityViewsData",
* "translation" = "Drupal\content_translation\ContentTranslationHandler",
* "route_provider" = {
* "html" = "Drupal\Core\Entity\Routing\AdminHtmlRouteProvider",
* },
* "form" = {
* "default" = "Drupal\mymodule\Entity\MyEntityForm",
* "edit" = "Drupal\mymodule\Entity\MyEntityForm",
* "delete" = "Drupal\Core\Entity\ContentEntityDeleteForm",
* }
* },
* admin_permission = "administer my entities",
* base_table = "myentity",
* data_table = "myentity_field_data",
* translatable = TRUE,
* entity_keys = {
* "id" = "eid",
* "bundle" = "subtype",
* "label" = "title",
* "langcode" = "langcode",
* "uuid" = "uuid",
* },
* links = {
* "canonical" = "/myentity/{myentity}",
* "delete-form" = "/myentity/{myentity}/delete",
* "edit-form" = "/myentity/{myentity}/edit",
* },
* field_ui_base_route = "entity.myentity_type.edit_form",
* bundle_entity_type = "myentity_type",
* )
*/
class
MyEntity
extends
ContentEntityBase
implements
MyEntityInterface
{
public
function
getTitle
()
{
return
$this
->
get
(
'title'
)
->
value
;
}
public
function
setTitle
(
$title
)
{
$this
->
set
(
'title'
,
$title
);
return
$this
;
}
public
static
function
baseFieldDefinitions
(
EntityTypeInterface
$entity_type
)
{
// Define base fields for the items in the entity_keys
// annotation. Note that as this is a static method, you cannot use
// $this->t() here; use t() for translation instead.
// Also note that the reason for the redundant descriptions is that
// Views displays errors if they are missing.
$fields
[
'eid'
]
=
BaseFieldDefinition
::
create
(
'integer'
)
->
setLabel
(
t
(
'My entity ID'
))
->
setDescription
(
t
(
'My entity ID'
))
->
setReadOnly
(
TRUE
)
->
setSetting
(
'unsigned'
,
TRUE
);
$fields
[
'subtype'
]
=
BaseFieldDefinition
::
create
(
'entity_reference'
)
->
setLabel
(
t
(
'Subtype'
))
->
setDescription
(
t
(
'Subtype'
))
->
setSetting
(
'target_type'
,
'myentity_type'
);
// Add a language code field so the entity can be translated.
$fields
[
'langcode'
]
=
BaseFieldDefinition
::
create
(
'language'
)
->
setLabel
(
t
(
'Language'
))
->
setDescription
(
t
(
'Language code'
))
->
setTranslatable
(
TRUE
)
->
setDisplayOptions
(
'view'
,
array
(
'type'
=>
'hidden'
,
))
->
setDisplayOptions
(
'form'
,
array
(
'type'
=>
'language_select'
,
'weight'
=>
2
,
));
// The title field is the only editable field in the base
// data. Set it up to be configurable in Manage Form Display
// and Manage Display.
$fields
[
'title'
]
=
BaseFieldDefinition
::
create
(
'string'
)
->
setLabel
(
t
(
'Title'
))
->
setDescription
(
t
(
'Title'
))
->
setTranslatable
(
TRUE
)
->
setRequired
(
TRUE
)
->
setDisplayOptions
(
'view'
,
array
(
'label'
=>
'hidden'
,
'type'
=>
'string'
,
'weight'
=>
5
,
))
->
setDisplayConfigurable
(
'view'
,
TRUE
)
->
setDisplayOptions
(
'form'
,
array
(
'type'
=>
'string_textfield'
,
'weight'
=>
5
,
))
->
setDisplayConfigurable
(
'form'
,
TRUE
);
$fields
[
'uuid'
]
=
BaseFieldDefinition
::
create
(
'uuid'
)
->
setLabel
(
t
(
'UUID'
))
->
setDescription
(
t
(
'Universally Unique ID'
))
->
setReadOnly
(
TRUE
);
return
$fields
;
}
}
A few notes on the annotation lines:
-
The first few lines of the annotation give the ID you chose, whether the entity is fieldable or not, and a human-readable label for the entity and its bundles. If your entity does not use bundles, leave out everything in this example that refers to them.
-
The permission defined in the
admin_permission
annotation must be defined in your mymodule.permissions.yml file. See “Drupal core’s main permission system”. -
The
base_table
andentity_keys
annotation sections define the database table and database table fields for the basic entity data.data_table
is used for translations. You can also indicaterevision_table
if your entity type should store revisions, in which case you should also add arevision
key to theentity_keys
section. Assuming your entity uses the default entity storage controller as your entity storage mechanism, these tables will be automatically set up for you. -
If your entity supports bundles, each bundle definition is a configuration entity. So, you’ll need to define a configuration entity type for your bundles and put its machine name (ID) in the
bundle_entity_type
annotation. To find out how to do this, see “Defining a Configuration Entity Type in Drupal 8”, which uses this exact example. -
“Implementing a plugin in a module” has more information about annotations in general.
The rest of the steps in the entity definition process create the classes that are referred to in the annotation header of your entity class, and set up other necessary data, functions, and so on.
Step 2: Define handlers
The handlers
section of the annotation lists the classes that govern
storage, access, and other operations on your entity objects. Drupal provides
default handlers for most of the operations; you can override the defaults by
adding entries to this part of the annotation. You’ll need to define the classes
for each specific handler you designate, if you’re not using a class provided by
Drupal; most entities, like this example,
will at least need custom edit and delete confirm forms.
The edit form should extend \Drupal\Core\Entity\ContentEntityForm
. In this
example, the annotation gives the class as
\Drupal\mymodule\Entity\MyEntityForm
, so it needs to go into the
src/Entity/MyEntityForm.php file under the main module directory. Typically, entity
forms will need to override the form()
, save()
, and possibly
validateForm()
methods; you may also need to override other methods for special cases,
such as buildEntity()
if taking form values and making an entity is special.
Here’s a basic example (look for Drupal core classes that extend
ContentEntityForm
for others); in this case, the base method for form()
is sufficient, as it will take care of the title and all of the
added fields:
namespace
Drupal\mymodule\Entity
;
use
Drupal\Core\Entity\ContentEntityForm
;
use
Drupal\Core\Form\FormStateInterface
;
use
Drupal\Core\Url
;
class
MyEntityForm
extends
ContentEntityForm
{
public
function
save
(
array
$form
,
FormStateInterface
$form_state
)
{
$entity
=
$this
->
entity
;
$entity
->
save
();
// You could do some logging here, set a message, and so on.
// Redirect to the entity display page.
$form_state
->
setRedirect
(
'entity.myentity.canonical'
,
array
(
'myentity'
=>
$entity
->
id
(
)));
}
}
The delete form should confirm that the user wants to delete the entity
object. The default class of \Drupal\Core\Entity\ContentEntityDeleteForm
is
sufficient for this entity; if not, you could extend this class (for instance, to
override the confirmation message).
This entity type is set up to use the default
\Drupal\Core\Entity\EntityViewBuilder
to build render arrays for viewing
entities with view modes. This relies on
the existence of a theme hook named 'myentity'
, which should render the
base properties of the entity. So, you’ll need to add this to the hook_theme()
implementation, the mymodule_theme()
function in mymodule.module:
function
mymodule_theme
(
$existing
,
$type
,
$theme
,
$path
)
{
return
array
(
'myentity'
=>
array
(
'render element'
=>
'elements'
,
'template'
=>
'myentity'
,
),
);
}
Then you’ll also need to create the templates/myentity.html.twig file to do the rendering:
<article{{ attributes }}> {% if not page %} <h2{{ title_attributes }}> <a href="{{ url }}" rel="bookmark">{{ title }}</a> </h2> {% endif %} <div {{ content_attributes }}> {{ content }} </div> </article>
And you’ll need a preprocess function to set up the template variables, which goes into the mymodule.module file:
use
Drupal\Core\Render\Element
;
use
Drupal\Core\Url
;
function
template_preprocess_myentity
(
&
$variables
)
{
$variables
[
'view_mode'
]
=
$variables
[
'elements'
][
'#view_mode'
];
$entity
=
$variables
[
'elements'
][
'#myentity'
];
$variables
[
'entity'
]
=
$entity
;
$variables
[
'title'
]
=
$variables
[
'entity'
]
->
getTitle
();
// See if the entity is being viewed on its own page.
$route_match
=
\Drupal
::
routeMatch
();
$page
=
FALSE
;
if
(
$variables
[
'view_mode'
]
==
'full'
&&
$route_match
->
getRouteName
()
==
'entity.myentity.canonical'
)
{
$page_entity
=
$route_match
->
getParameter
(
'myentity'
);
if
(
$page_entity
&&
$page_entity
->
id
()
==
$entity
->
id
())
{
$page
=
TRUE
;
}
}
$variables
[
'page'
]
=
$page
;
// Set up content variable for templates.
$variables
+=
array
(
'content'
=>
array
());
foreach
(
Element
::
children
(
$variables
[
'elements'
])
as
$key
)
{
$variables
[
'content'
][
$key
]
=
$variables
[
'elements'
][
$key
];
}
// Set up attributes.
$variables
[
'attributes'
][
'class'
][]
=
'myentity'
;
}
The list_builder
and views_data
handlers are for Views integration,
which is covered in “Step 4: Add Views integration”. This entity type is
translatable, so it needs to define a translation
handler, but the default
one from the core Content Translation module is sufficient. The routing handler
is discussed in the next section.
Step 3: Set up routing and links
The links
annotation section lists the URL paths for the
basic operations. If your entity can
be displayed on its own page, you’ll need to define the canonical
link to be
the viewing page; otherwise, your canonical link should probably be the edit
page. Most
content entities also need delete-form
and edit-form
links. If your entity
type is fieldable, then you also need to define the field_ui_base_route
annotation property (outside of the links section), because this
route is used by the Field UI module to set up the management pages for fields,
forms, and display modes. For nonbundle entities, it should be a page for
the settings for the entity type; for entities with bundles, it should be the
page where you edit the bundle. This example uses bundles, so the
route it uses is defined later, as part of defining the bundle configuration
entity type.
You have two options for defining
the corresponding routes, so that
the URLs will actually work. The first option is to define them,
with names like entity.myentity.canonical
,
in your mymodule.routing.yml file. The configuration
entity example in this chapter does this; see “Defining a Configuration Entity Type in Drupal 8” for details.
The second option, which is used in this
example and in most Drupal core content entities, is to use an entity route
provider class. Drupal provides two:
\Drupal\Core\Entity\Routing\DefaultHtmlRouteProvider
provides canonical,
edit-form, and delete-form routes;
\Drupal\Core\Entity\Routing\AdminHtmlRouteProvider
(used in this example)
provides the same routes, set up so that when you are editing or deleting
entities, the administrative theme is used. You can also extend one of these
classes to provide routes in a different way; look at
\Drupal\node\Entity\NodeRouteProvider
or other classes that implement
\Drupal\Core\Entity\Routing\EntityRouteProviderInterface
for examples.
The classes for the entity forms have already been defined, and this example uses the default entity viewer for the entity page, with a placeholder that loads the entity. You could also override the default class to define your own view handler.
For most entities, if you are on the entity viewing page, you would want to see a tab or link to edit the entity. This can be accomplished via a local task set, which goes into the mymodule.links.task.yml file:
entity.myentity.canonical: title: 'View' route_name: entity.myentity.canonical base_route: entity.myentity.canonical entity.myentity.edit_form: title: 'Edit' route_name: entity.myentity.edit_form base_route: entity.myentity.canonical
You’ll also need to think about the process for adding a new entity of this type. For instance, to add a node, you need to choose which type (bundle) of node you’re adding, and then visit example.com/node/add/thetypename. Taxonomy terms work in a similar way, with an add page that depends on the vocabulary (bundle). But you don’t add a comment by bundle—comments are added to a specific entity object. For this example, we’ll make it so that you add a new entity object from the entity subtype (bundle) management page, so that will be explained in conjunction with defining the bundle configuration entity type, in “Defining a Configuration Entity Type in Drupal 8”.
Further reading and reference:
-
Parameter upcasting in routes: http://bit.ly/parameter_upcasting
Step 4: Add Views integration
Your content entity type will probably also need to have an administrative page, which would presumably list existing entities with edit and delete links, and allow you to add new entities. The best way to create an administrative page is by providing a default view. In order to do that, you’ll need to provide Views integration for your entity, as well as a list builder.
Both the Views integration and the list builder are specified in the handlers
section of your entity annotation, as views_data
and list_builder
. The
entity here uses the default entity list builder class but has its own views
data class \Drupal\mymodule\Entity\MyEntityViewsData
. This class
provides the same kind of output as hook_views_data()
(see
“Providing a New Views Data Source”), but most of it is provided in an automated way by the
base class. In this case, the class goes in
src/Entity/MyEntityViewsData.php under the module directory:
namespace
Drupal\mymodule\Entity
;
use
Drupal\views\EntityViewsData
;
class
MyEntityViewsData
extends
EntityViewsData
{
public
function
getViewsData
()
{
// Start with the Views information provided by the base class.
$data
=
parent
::
getViewsData
();
// Override a few things...
// Define a wizard.
$data
[
'myentity_field_data'
][
'table'
][
'wizard_id'
]
=
'myentity'
;
// You could also override labels or put in a custom field
// or filter handler.
return
$data
;
}
}
This class, in turn, refers to a wizard plugin, which is a plugin class that
enables a user to go to the “Add new view” page at admin/structure/views/add
and choose your entity from the
list to create a new view. The preceding code tells Views
that the plugin ID for this wizard is myentity
. For Views to find the plugin,
it needs to be in the src/Plugins/views/wizard directory (and corresponding
namespace). Let’s call the class MyEntityViewsWizard
and just accept the
default behavior of a Views wizard:
namespace
Drupal\mymodule\Plugin\views\wizard
;
use
Drupal\Core\Form\FormStateInterface
;
use
Drupal\views\Plugin\views\wizard\WizardPluginBase
;
/**
* Provides a views wizard for My Entity entities.
*
* @ViewsWizard(
* id = "myentity",
* base_table = "myentity_field_data",
* title = @Translation("My Entity")
* )
*/
class
MyEntityViewsWizard
extends
WizardPluginBase
{
}
This is sufficient to provide Views integration for the entity. So, to provide an administrative interface for managing the My Entity content, you’d also need to:
-
Define a view using the Views user interface. It should include exposed filters; sorting (usually via table headers); and links to add, edit, and delete your entities.
-
Export the view as a YAML configuration file. Edit the file manually and remove the UUID line, because the export will contain a UUID, but supplied configuration shouldn’t. Place the configuration file in your module’s config/install or config/optional directory. See “Configuration file format and schema in Drupal 8”.
Note that if the only available management page for your entity uses Views, you might want to make the Views module a dependency of your module, because if Views is not enabled, users will not be able to manage their entities.
Step 5: Enable your module
If you are using bundles, your content entity type will not work until you’ve also defined the configuration entity type for the bundle (see “Defining a Configuration Entity Type in Drupal 8”).
If you are not using bundles, enable your module and the entity type should be defined. If your module is already enabled, you should be able to get by with a container rebuild (see “Rebuilding the container”), but you may need to uninstall your module and reinstall it. You should probably test your entity type module in a test installation of Drupal until you’ve verified that it’s working, though, or at least make frequent database backups, because you may get Drupal into an unrecoverable state during the debugging phase.
Defining a Configuration Entity Type in Drupal 8
Defining a configuration entity type is somewhat similar to defining a content entity type (described in the previous section, “Defining a Content Entity Type in Drupal 8”), but the process has several differences. The basic steps are listed in this section; as an example, we’ll define the content entity bundle configuration entity needed for the content entity type defined “Defining a Content Entity Type in Drupal 8”.
If you need to define a configuration entity for a generic configuration purpose rather than as a bundle for a content entity, a good example to look at is the entity for date format configuration in the Drupal core DateTime library. Accordingly, this section also points out which classes and files are used to define this entity, so that you can follow along with that example as well.
Before you start, you will need to choose a machine name for your configuration
entity, and a configuration prefix for configuration data storage. The
configuration prefix defaults to $module_name.$machine_name
, or
core.$machine_name
for entities defined in Drupal core outside of modules,
but you can
shorten the suffix (after the module name) if you want, by adding
config_prefix
to the annotation to your configuration entity class header. For
our bundle example, the machine name myentity_type
was specified in the
content entity type annotation, and we’ll leave the configuration prefix as
mymodule.myentity_type
. For the date format entity, the machine name is
date_format
and the prefix is core.date_format
.
The maximum length for an entity type machine name or configuration prefix is 32 characters.
Further reading and reference:
-
http://bit.ly/config_entitites has some more documentation about configuration entities.
Step 1: Define the configuration schema
The first step in defining a configuration entity type is to define a
configuration schema for mymodule.myentity_type.*
in the
config/schema/mymodule.schema.yml file that defines the fields for your
configuration data. For the date format example, see the core.date_format
section of core/config/schema/core.data_types.schema.yml; for our bundle
example:
mymodule.myentity_type.*: type: config_entity label: 'My entity subtype' mapping: id: type: string label: 'Machine-readable name' label: type: label label: 'Name' description: type: text label: 'Description' settings: label: 'Settings' type: mymodule.settings.myentity mymodule.settings.myentity: type: mapping label: 'My entity subtype settings' mapping: default_status: type: boolean label: 'Published by default'
Note that the 'Published by default'
setting is just for illustration;
the content entity does not actually have a 'published'
property.
Further reading and reference:
Step 2: Define the entity interface and class
The next step is to define an entity interface and class for your configuration
entity. The interface
should extend \Drupal\Core\Config\Entity\ConfigEntityInterface
and may define
a few additional methods that your configuration entities will need. In the date
format example, this interface is \Drupal\Core\Datetime\DateFormatInterface
; in our
bundle example, it’s
\Drupal\mymodule\Entity\MyEntityTypeInterface
, which goes in the
src/Entity/MyEntityTypeInterface.php file under the module directory:
namespace
Drupal\mymodule\Entity
;
use
Drupal\Core\Config\Entity\ConfigEntityInterface
;
interface
MyEntityTypeInterface
extends
ConfigEntityInterface
{
public
function
getDescription
();
}
Note that even if you do not need to define additional methods, it is still a good idea to define an interface, so that you can declare objects to be of that type (leading to better self-documenting code).
The entity class
should extend \Drupal\Core\Config\Entity\ConfigEntityBase
and implement your
entity interface. For a configuration entity being used as an entity
bundle, there is another base class to use:
Drupal\Core\Config\Entity\ConfigEntityBundleBase
, which contains some
additional helper code. Whichever base class is used, your class
also needs to be annotated with
\Drupal\Core\Entity\Annotation\ConfigEntityType
annotation in its
documentation header.
In the date format example, this class is
\Drupal\Core\Datetime\Entity\DateFormat
; in our bundle example, the class is
\Drupal\mymodule\Entity\MyEntityType
, which goes in the
src/Entity/MyEntityType.php file under the module directory:
namespace
Drupal\mymodule\Entity
;
use
Drupal\Core\Config\ConfigException
;
use
Drupal\Core\Config\Entity\ConfigEntityBundleBase
;
use
Drupal\Core\Entity\EntityStorageInterface
;
use
Drupal\mymodule\Entity\MyEntityTypeInterface
;
/**
* Defines the My Entity bundle configuration entity.
*
* @ConfigEntityType(
* id = "myentity_type",
* label = @Translation("My entity subtype"),
* handlers = {
* "form" = {
* "add" = "Drupal\mymodule\Entity\MyEntityTypeForm",
* "edit" = "Drupal\mymodule\Entity\MyEntityTypeForm",
* "delete" = "Drupal\mymodule\Entity\MyEntityTypeDeleteForm",
* },
* "list_builder" = "Drupal\mymodule\Entity\MyEntityTypeListBuilder",
* },
* admin_permission = "administer my entities",
* bundle_of = "myentity",
* entity_keys = {
* "id" = "id",
* "label" = "label",
* },
* links = {
* "add-form" = "/admin/structure/myentity_type/add",
* "edit-form" = "/admin/structure/myentity_type/manage/{myentity_type}",
* "delete-form" = "/admin/structure/myentity_type/delete/{myentity_type}",
* }
* )
*/
class
MyEntityType
extends
ConfigEntityBundleBase
implements
MyEntityTypeInterface
{
// Machine name or ID of the entity bundle.
public
$id
;
// Human-readable name of the entity bundle.
public
$label
;
// Description of the entity bundle.
public
$description
;
// Settings for the entity bundle.
public
$settings
=
array
();
public
function
getDescription
()
{
return
$this
->
description
;
}
public
function
preSave
(
EntityStorageInterface
$storage
)
{
parent
::
preSave
(
$storage
);
if
(
!
$this
->
isNew
()
&&
(
$this
->
getOriginalId
()
!=
$this
->
id
()))
{
throw
new
ConfigException
(
'Cannot change machine name'
);
}
}
}
A few notes on the annotation lines:
-
The first few lines of the annotation give the ID you chose and a human-readable label for the entity.
-
For some configuration entities, you will need to define permissions for administering your entity in your mymodule.permissions.yml file. This permission is referenced in the annotation on your entity class; you can also use an existing permission from another module (if so, make sure that this module is listed as a dependency of your module).
-
The
bundle_of
annotation gives the machine name of the entity that this entity is a bundle type for. Omit for generic configuration entities. -
The
entity_keys
annotation section lists the mapping between the entity ID and label to the configuration schema fields that you’ve defined. -
The rest of the annotation is discussed in the following steps.
-
“Implementing a plugin in a module” has more information about annotations.
Step 3: Define handlers
The handlers
section of the annotation lists classes that handle
storage, access, and other operations on your entity objects. Drupal provides
default handler classes for most of the operations; you can override the
defaults by
adding entries to this list. For configuration entities, you will need to define
the form for adding and editing, the confirmation form for deleting, and the
list builder, which is used to build an administrative screen to manage your
configuration items.
The editing form should extend \Drupal\Core\Entity\EntityForm
; usually, the
only methods you need to override are form()
, save()
, and
possibly validateForm()
.
In the date format example, this class is
\Drupal\system\Form\DateFormatEditForm
; in our bundle example, the class is
given in the
annotation as \Drupal\mymodule\Entity\MyEntityTypeForm
, so it needs to go in
file src/Entity/MyEntityTypeForm.php under the main module directory:
namespace
Drupal\mymodule\Entity
;
use
Drupal\Core\Entity\EntityForm
;
use
Drupal\Core\Form\FormStateInterface
;
use
Drupal\Core\Url
;
class
MyEntityTypeForm
extends
EntityForm
{
public
function
form
(
array
$form
,
FormStateInterface
$form_state
)
{
$form
=
parent
::
form
(
$form
,
$form_state
);
$form
[
'id'
]
=
array
(
'#title'
=>
$this
->
t
(
'Machine-readable name'
),
'#type'
=>
'textfield'
,
'#required'
=>
TRUE
,
);
// If we are editing an existing entity, show the current ID and
// do not allow it to be changed.
if
(
$this
->
entity
->
id
())
{
$form
[
'id'
][
'#default_value'
]
=
$this
->
entity
->
id
();
$form
[
'id'
][
'#disabled'
]
=
TRUE
;
}
$form
[
'label'
]
=
array
(
'#title'
=>
$this
->
t
(
'Label'
),
'#type'
=>
'textfield'
,
'#default_value'
=>
$this
->
entity
->
label
,
);
$form
[
'description'
]
=
array
(
'#title'
=>
$this
->
t
(
'Description'
),
'#type'
=>
'textfield'
,
'#default_value'
=>
$this
->
entity
->
description
,
);
$form
[
'settings'
]
=
array
(
'#type'
=>
'details'
,
'#title'
=>
$this
->
t
(
'Settings'
),
'#open'
=>
TRUE
,
);
$settings
=
$this
->
entity
->
settings
;
$form
[
'settings'
][
'default_status'
]
=
array
(
'#title'
=>
$this
->
t
(
'Published by default'
),
'#type'
=>
'checkbox'
,
);
if
(
isset
(
$settings
[
'default_status'
])
&&
$settings
[
'default_status'
])
{
$form
[
'settings'
][
'default_status'
][
'#default_value'
]
=
TRUE
;
}
return
$form
;
}
public
function
validateForm
(
array
&
$form
,
FormStateInterface
$form_state
)
{
parent
::
validateForm
(
$form
,
$form_state
);
$values
=
$form_state
->
getValues
();
// Require non-empty ID.
$id
=
trim
(
$values
[
'id'
]);
if
(
empty
(
$id
))
{
$form_state
->
setErrorByName
(
'id'
,
$this
->
t
(
'Subtype names must not be empty'
));
}
}
public
function
save
(
array
$form
,
FormStateInterface
$form_state
)
{
$type
=
$this
->
entity
;
$type
->
save
();
// You could do some logging here, set a message, and so on.
// Redirect to admin page.
$form_state
->
setRedirect
(
new
Url
(
'mymodule.my_entity_type_list'
));
}
}
The delete form should confirm that the user wants to delete the
configuration item and should extend
\Drupal\Core\Entity\EntityConfirmFormBase
. For this class, you will
need to specify (in methods) the text for the question to ask the
user, what to do if delete is confirmed, and the URL (route) to go to if the
action is canceled. In the date format
example, this class is \Drupal\system\Form\DateFormatDeleteForm
.
Our example is a bit more complicated:
you should not delete an entity bundle if there would be any corresponding
entity objects left over. Some entity types (such as node—see
\Drupal\node\Form\NodeTypeDeleteConfirm
) handle this by disallowing bundle
deletion if entities exist of that type, and others delete the corresponding
entities (of course, warning the user). For this example, in order to illustrate
how to do it, we’ll take the latter tactic and delete the entities.
The delete form class is given in the annotation
as \Drupal\mymodule\Entity\MyEntityTypeDeleteForm
, and it needs to go in the
src/Entity/MyEntityTypeDeleteForm.php file under the main module directory:
namespace
Drupal\mymodule\Entity
;
use
Drupal\Core\Entity\EntityConfirmFormBase
;
use
Drupal\Core\Entity\EntityTypeManagerInterface
;
use
Drupal\Core\Entity\Query\QueryFactory
;
use
Drupal\Core\Form\FormStateInterface
;
use
Drupal\Core\Url
;
use
Symfony\Component\DependencyInjection\ContainerInterface
;
class
MyEntityTypeDeleteForm
extends
EntityConfirmFormBase
{
protected
$manager
;
protected
$queryFactory
;
public
function
__construct
(
QueryFactory
$query_factory
,
EntityTypeManagerInterface
$manager
)
{
$this
->
queryFactory
=
$query_factory
;
$this
->
manager
=
$manager
;
}
public
static
function
create
(
ContainerInterface
$container
)
{
return
new
static
(
$container
->
get
(
'entity.query'
),
$container
->
get
(
'entity_type.manager'
)
);
}
public
function
getQuestion
()
{
return
$this
->
t
(
'Are you sure you want to delete %label?'
,
array
(
'%label'
=>
$this
->
entity
->
label
()));
}
public
function
getDescription
()
{
return
$this
->
t
(
'All entities of this type will also be deleted!'
);
}
public
function
getCancelUrl
()
{
return
new
Url
(
'mymodule.myentity_type.list'
);
}
public
function
submitForm
(
array
&
$form
,
FormStateInterface
$form_state
)
{
// Find all the entities of this type, using an entity query.
$query
=
$this
->
queryFactory
->
get
(
'myentity'
);
$query
->
condition
(
'subtype'
,
$this
->
entity
->
id
());
$ids
=
$query
->
execute
();
// Delete the found entities, using the storage handler.
// You may actually need to use a batch here, if there could be
// many entities.
$storage
=
$this
->
manager
->
getStorage
(
'myentity'
);
$entities
=
$storage
->
loadMultiple
(
$ids
);
$storage
->
delete
(
$entities
);
// Delete the bundle entity itself.
$this
->
entity
->
delete
();
$form_state
->
setRedirectUrl
(
$this
->
getCancelUrl
());
}
}
The list builder class makes an
administration overview page for managing your configuration data; it should
usually extend \Drupal\Core\Config\Entity\ConfigEntityListBuilder
. In the
date format example, this class is \Drupal\system\DateFormatListBuilder
; in
our bundle example, the annotation gives the class name as
\Drupal\mymodule\Entity\MyEntityTypeListBuilder
, so it needs to go in the
src/Entity/MyEntityTypeListBuilder.php file under the main module directory:
namespace
Drupal\mymodule\Entity
;
use
Drupal\Core\Config\Entity\ConfigEntityListBuilder
;
use
Drupal\Core\Entity\EntityInterface
;
use
Drupal\Component\Utility\Xss
;
class
MyEntityTypeListBuilder
extends
ConfigEntityListBuilder
{
public
function
buildHeader
()
{
$header
[
'label'
]
=
$this
->
t
(
'Label'
);
$header
[
'description'
]
=
array
(
'data'
=>
$this
->
t
(
'Description'
),
);
return
$header
+
parent
::
buildHeader
();
}
public
function
buildRow
(
EntityInterface
$entity
)
{
$row
[
'label'
]
=
array
(
'data'
=>
$this
->
getLabel
(
$entity
),
);
$row
[
'description'
]
=
Xss
::
filterAdmin
(
$entity
->
description
);
return
$row
+
parent
::
buildRow
(
$entity
);
}
}
Step 4: Define routing and route controllers
Finally, in your module’s routing file, you will need to define administrative
routes for managing your configuration entity: for the paths in the
links
annotation and various others. You’ll also probably want to
add an administrative menu entry for your overview page, so users will be able
to find it. In the date format example, the administrative routes are defined in
core/modules/system/system.routing.yml, the menu entry is in
core/modules/system/system.links.menu.yml, and there are local actions and
tasks defined in core/modules/system/system.links.action.yml and
core/modules/system/system.links.task.yml, respectively.
For the bundle example, the following routes go into the mymodule.routing.yml file for the administrative actions on the entity subtypes:
mymodule.myentity_type.list: path: '/admin/structure/myentity_type' defaults: _entity_list: 'myentity_type' _title: 'My entity subtypes' requirements: _permission: 'administer my entities' entity.myentity_type.add_form: path: '/admin/structure/myentity_type/add' defaults: _entity_form: 'myentity_type.add' _title: 'Add my entity subtype' requirements: _entity_create_access: 'myentity_type' entity.myentity_type.edit_form: path: '/admin/structure/myentity_type/manage/{myentity_type}' defaults: _entity_form: 'myentity_type.edit' _title: 'Edit my entity subtype' requirements: _entity_access: 'myentity_type.edit' entity.myentity_type.delete_form: path: '/admin/structure/myentity_type/delete/{myentity_type}' defaults: _entity_form: 'myentity_type.delete' _title: 'Delete my entity subtype' requirements: _entity_access: 'myentity_type.delete'
Note
Instead of defining routes as shown here in your mymodule.routing.yml file, you can use an entity route provider class. The content entity example in this chapter does this (see “Defining a Content Entity Type in Drupal 8”); it is less common for configuration entities.
Also, this menu link entry goes into mymodule.links.menu.yml, to make the administration page visible in the Structure administrative section:
mymodule.myentity_type.list: title: My entity subtypes description: Manage my entity subtypes and their fields, display, etc. route_name: mymodule.myentity_type.list parent: system.admin_structure
In order to make an Add link visible on the list page, the following goes into the mymodule.links.action.yml file:
entity.myentity_type.add_form: route_name: entity.myentity_type.add_form title: 'Add my entity subtype' appears_on: - mymodule.myentity_type.list
The Field UI module makes a set of local tasks for managing fields, view modes, and form modes on fieldable entities. In order to make the entity subtype editing form part of this set of tasks, the following goes into the mymodule.links.task.yml file:
entity.myentity_type.edit_form: title: 'Edit' route_name: entity.myentity_type.edit_form base_route: entity.myentity_type.edit_form
And finally, during definition of the content entity, we decided that we’d handle adding new content entity objects from the entity subtype bundle management page. So what we want to happen is that once you have an entity subtype defined, one of the actions available to you (besides Edit, Delete, Manage fields, etc.) would be to add a new entity object.
To accomplish this, we need this route in the mymodule.routing.yml file:
mymodule.myentity.add: path: '/myentity/add/{myentity_type}' defaults: _controller: '\Drupal\mymodule\Controller\MyUrlController::addEntityPage' _title: 'Add new my entity' requirements: _entity_create_access: 'myentity'
We also need to define the controller method to put up the page, which goes into src/Controller/MyUrlController.php:
namespace
Drupal\mymodule\Controller
;
use
Drupal\mymodule\Entity\MyEntityTypeInterface
;
class
MyUrlController
extends
ControllerBase
{
public
function
addEntityPage
(
MyEntityTypeInterface
$type
)
{
// Create a stub entity of this type.
$entity
=
$this
->
entityTypeManager
()
->
getStorage
(
'myentity'
)
->
create
(
array
(
'subtype'
=>
$type
->
id
()));
// You might want to set other values on the stub entity.
// Return the entity editing form for the stub entity.
return
$this
->
entityFormBuilder
()
->
getForm
(
$entity
);
}
}
To make this appear in the operations list for the entity subtype, we need to
override the getDefaultOperations()
method in the
MyEntityListBuilder
list builder class defined earlier and add a new use
statement at the top:
// At the top.
use
Drupal\Core\Url
;
// New method.
public
function
getDefaultOperations
(
EntityInterface
$entity
)
{
$operations
=
parent
::
getDefaultOperations
(
$entity
);
// Add an operation for adding a new entity object of this type.
$url
=
new
Url
(
'mymodule.myentity.add'
,
array
(
'myentity_type'
=>
$entity
->
id
()));
$operations
[
'add_new'
]
=
array
(
'title'
=>
$this
->
t
(
'Add new My Entity'
),
'weight'
=>
11
,
'url'
=>
$url
,
);
return
$operations
;
}
Step 5: Enable your module
Once you have all of these files created and code written, enable your module and your entity type should be defined. If your module is already enabled, you may be able to get by with a container rebuild (see “Rebuilding the container”), but you may need to uninstall your module and reinstall it. You should probably test your entity type module in a test installation of Drupal until you’ve verified that it’s working, though, or at least make frequent database backups, because you may get Drupal into an unrecoverable state during the debugging phase.
Querying and Loading Entities in Drupal 8
Although you can technically use the basic Database API described in “Querying the Database with the Database API” to query entities—or, worse yet, you could use the base PHP functions for MySQL queries—in Drupal 8 this is strongly discouraged. The reason is that content entity storage is a service in Drupal 8, to allow sites (in principle, anyway) to use alternative storage mechanisms, such as MongoDB or other non-SQL methods, to store entities. So although the default content entity storage is in the main Drupal database, it is a good idea to use entity queries to query entities, rather than the basic Database API.
Entity queries are objects that implement
\Drupal\Core\Entity\Query\QueryInterface
(for regular queries), or
\Drupal\Core\Entity\Query\QueryAggregateInterface
(for aggregate queries). You
can retrieve the appropriate query object from the entity.query
service, as
follows:
// Code without dependency injection or $container variable:
$query
=
\Drupal
::
entityQuery
(
'myentity'
);
$query
=
\Drupal
::
entityQueryAggregate
(
'myentity'
);
// Using dependency injection or a $container variable in a class:
$query_service
=
$container
->
get
(
'entity.query'
);
$query
=
$query_service
->
get
(
'myentity'
);
$query
=
$query_service
->
getAggregate
(
'myentity'
);
Once you have a query object, you can use the condition()
method to add field
or base data conditions, and then execute the query. For example, to find all
the entities of a given bundle:
// Generic entities with 'bundle' property:
$query
->
condition
(
'bundle'
,
'mybundle'
);
// Node entities:
$query
->
condition
(
'type'
,
'mytype'
);
$ids
=
$query
->
execute
();
The result of a query will be a list of the matching entity IDs. To load them,
you should use the entity storage manager, which is an object that implements
\Drupal\Core\Entity\EntityStorageInterface
, which you can retrieve from the
entity_type.manager
service:
// Code without dependency injection or $container variable:
$storage
=
\Drupal
::
entityTypeManager
()
->
getStorage
(
'myentity'
);
// Dependency injection or $container variable:
$storage
=
$container
->
get
(
'entity_type.manager'
)
->
getStorage
(
'myentity'
);
// Load entities:
$entities
=
$storage
->
loadMultiple
(
$ids
);
The result will be an array of loaded entity objects, keyed by the entity IDs.
Further reading and reference:
Defining a Field Type
If you need to attach data to nodes or other entity types, you need to find a field type that stores this type of data. Between Drupal core and contributed modules, there are field types available for most of the common use cases for fielded content (plain text, numbers, formatted text, dates, images, media attachments, etc.), so if you are building a website, and you need to store a particular type of data that is not covered by the fields in Drupal core, start by searching contributed modules for a field type that will suit your needs.
Keep in mind that the field type only defines the stored data, while the formatter defines the display of the data and the widget defines the method for data input. So instead of defining a field, you may only need a custom widget or formatter for your use case. Here are several examples:
-
You need to store plain text data, based on clicking in a region on an image or using a Flash-based custom input method. For this use case, use a core Text field for storage, and create a custom widget for data input.
-
You need to select one of several predefined choices on input, and display a predefined icon or canned text on output based on that choice. For this use case, use a core Number field for storage, and a core Select widget for input (with text labels; you could also use a core Text field for storage). Create a custom formatter for display.
-
You are creating a website that displays company profiles, using a Company node content type. For each company content item, you need to attach several office locations. For this use case, use the contributed Geofield, Location, Address Field, or another geographical information field module rather than defining your own custom field (try module category “Location” to find more).
-
For this same Company content type, you need several related fields to be grouped together on input and display; for instance, you might want to group the company size, annual revenue, and other similar fields together under Statistics. For this use case, use the Field Group contributed module to group the fields rather than creating a custom field type module.
-
For this same Company content type, you need to keep track of staff people, where each staff person has a profile with several fields. For this use case, create a separate Staff node content type, and use the contributed Entity Reference or Relation module to relate staff people to companies or companies to staff people; the Entity Reference field is included in Drupal core version 8. Or, use the Field Collection contributed module to create a staff field collection that is attached to the Company content type.
-
You have a field collection use case similar to the Staff of Company example, but you feel that it is general enough that many other websites would want to use this same field collection. In this case, it may make sense to create a custom field module and contribute it to drupal.org so that others can use it. Alternatively, you could use a the Field Collection contributed module, and export your collection configuration using the Features module (Drupal 7) or Drupal core configuration export (Drupal 8).
See “Finding Drupal add-ons” for hints on locating contributed modules. Note that some of the modules mentioned here may not yet be available for Drupal 8.
The remainder of this section describes how to define a new field type, if you’ve decided that this is what you need. Widgets and formatters are covered in “Programming with Field Widgets” and “Programming with Field Formatters”, respectively.
Defining a field type in Drupal 7
Assuming that you have decided you need a custom field module, here is an overview of how to define a field type in Drupal 7:
-
Implement
hook_field_info()
in your mymodule.module file to provide basic information about your field type (such as the label used to select it when attaching a field to an entity bundle in the administrative user interface). -
Implement
hook_field_schema()
in your mymodule.install file to provide information about the data stored in your field. This defines database fields in a way similar tohook_schema()
, but it is not exactly the same. -
Set up a widget for editing the field, and a formatter for displaying it (see the following sections).
There are many fields defined by Drupal core and contributed modules, so rather than providing another programming example here, I’ll just suggest that you use one of the following as a starting point:
-
A Drupal core field module (Image, File, Text, List, Number, or Taxonomy).
-
The documentation for the two field hooks. These are part of Drupal core, in the modules/field/field.api.php file (or look them up on https://api.drupal.org).
-
Date, Link, or another contributed field module (search modules for category “Fields”).
-
The Field example in Examples for Developers, which has some extra documentation explaining what is going on.
Further reading and reference:
Defining a field type in Drupal 8
In Drupal 8, field types are plugins. So, to define one in a module, you need to:
-
Define a class in the
Plugin\Field\FieldType
namespace under your module’s namespace, therefore located in the src/Plugin/Field/FieldType directory. The class needs to implement\Drupal\Core\Field\FieldItemInterface
, and typically extends\Drupal\Core\Field\FieldItemBase
. You’ll usually just need to define methodspropertyDefinitions()
andschema()
. -
Annotate it with
\Drupal\Core\Field\Annotation\FieldType
annotation.
See “The Basics of Drupal 8 Plugin Programming” for a more detailed overview of the plugin system.
There are many good examples in Drupal core of field types, and they’re fairly simple to do, so I have not provided another one here. You can find them listed on the FieldType annotation class page on https://api.drupal.org.
Programming with Field Widgets
There are several reasons that you may need to do some programming with field widgets:
-
If you have defined your own custom field type, you will need to define a widget for entering data for that field or repurpose an existing widget for use on your field.
-
You may need to define a custom input method for an existing field type.
-
You may be want to repurpose an existing widget for use on a different field type.
This section covers both how to define a new widget and how to repurpose an existing widget.
Defining a field widget in Drupal 7
To define a field widget in Drupal 7, you need to implement two hooks in your
mymodule.module file: hook_field_widget_info()
and
hook_field_widget_form()
; the latter uses the Form API. If
you’re defining a field widget for a custom field type that you’ve defined, I
suggest going back to the field type module you used as a starting point and
using that module’s widget as a starting point for your
widget.
If you’re defining a new widget for an existing field, the following example may be helpful. Assume that you want to define a widget for the core Text field that provides a custom method for input of plain text data, which could use Flash, JavaScript, or an image map to let users click on a region on an image or map, and store their choice as a predefined text string in the field. As a proxy for the custom input method, this example just uses an HTML select element; if you really just need an HTML select for your site, you could use a Drupal core “List (text)” field and choose the “Select list” widget.
Here are the two hook implementations:
// Provide information about the widget.
function
mymodule_field_widget_info
()
{
return
array
(
// Machine name of the widget.
'mymodule_mywidget'
=>
array
(
// Label for the administrative UI.
'label'
=>
t
(
'Custom text input'
),
// Field types it supports.
'field types'
=>
array
(
'text'
),
),
// Define additional widgets here, if desired.
);
}
// Set up an editing form.
// Return a Form API form array.
function
mymodule_field_widget_form
(
&
$form
,
&
$form_state
,
$field
,
$instance
,
$langcode
,
$items
,
$delta
,
$element
)
{
// Verify the widget type. Only needed if you define more than one widget.
if
(
$instance
[
'widget'
][
'type'
]
==
'mymodule_mywidget'
)
{
// Find the current text field value.
$value
=
isset
(
$items
[
$delta
][
'value'
])
?
$items
[
$delta
][
'value'
]
:
NULL
;
// Set up the editing form element. Substitute your custom
// code here, instead of using an HTML select.
$element
[
'value'
]
=
array
(
'#type'
=>
'select'
,
'#options'
=>
array
(
'x_stored'
=>
t
(
'x label'
),
'y_stored'
=>
t
(
'y label'
)),
'#default_value'
=>
$value
,
);
}
return
$element
;
}
Further reading and reference:
Defining a field widget in Drupal 8
In Drupal 8, field widgets are plugins. So, to define one in a module, you need to:
-
Define a class in the
Plugin\Field\FieldWidget
namespace under your module’s namespace, therefore located in the src/Plugin/Field/FieldWidget directory. The class needs to implement\Drupal\Core\Field\WidgetInterface
, and it typically extends\Drupal\Core\Field\WidgetBase
or a more specific base class that extends it. You’ll need to define theformElement()
method (which gives the widget editing form), and you may need to override additional default methods on the base class. -
Annotate it with
\Drupal\Core\Field\Annotation\FieldWidget
annotation.
If you’re defining a field widget for a custom field type that you’ve defined, I suggest going back to the field type module you used as a starting point and using that module’s widget as a starting point for your widget.
If you’re defining a new widget for an existing field, the following example may be helpful. Assume that you want to define a widget for the core plain Text field that provides a custom method for input of plain text data, which could use Flash, JavaScript, or an image map to let the user click on a region on an image or map, and store their choice as a predefined text string in the field. As a proxy for the custom input method, this example just uses an HTML select element; if you really just need an HTML select for your site, you could use a Drupal core “List (text)” field and choose the “Select list” widget.
Here is the widget class, which needs to go into the src/Plugin/Field/FieldWidget/MyCustomText.php file under the main module directory:
namespace
Drupal\mymodule\Plugin\Field\FieldWidget
;
use
Drupal\Core\Field\WidgetBase
;
use
Drupal\Core\Field\FieldItemListInterface
;
use
Drupal\Core\Form\FormStateInterface
;
/**
* Custom widget for choosing an option from a map.
*
* @FieldWidget(
* id = "mymodule_mywidget",
* label = @Translation("Custom text input"),
* field_types = {
* "string"
* }
* )
*/
class
MyCustomText
extends
WidgetBase
{
public
function
formElement
(
FieldItemListInterface
$items
,
$delta
,
array
$element
,
array
&
$form
,
FormStateInterface
$form_state
)
{
$value
=
isset
(
$items
[
$delta
]
->
value
)
?
$items
[
$delta
]
->
value
:
NULL
;
// Set up the editing form element. Substitute your custom
// code here, instead of using an HTML select.
$element
[
'value'
]
=
$element
+
array
(
'#type'
=>
'select'
,
'#options'
=>
array
(
'x_stored'
=>
$this
->
t
(
'x label'
),
'y_stored'
=>
$this
->
t
(
'y label'
),
),
'#default_value'
=>
$value
,
);
return
$element
;
}
}
Further reading and reference:
Examples—field widgets:
-
There are many good examples of field widgets in Drupal core. You can find them listed on https://api.drupal.org on the page for the
FieldWidget
annotation class.
Repurposing an existing field widget
Because the module that defines the widget tells Drupal what field types it
supports in its hook_field_widget_info()
implementation (Drupal 7) or plugin
annotation (Drupal 8), if you want to
repurpose an existing widget to apply to a different field type, you need to
implement hook_field_widget_info_alter()
in your mymodule.module file. This
hook allows you to alter the information collected from all other modules’ hooks
or plugins. For example:
function
mymodule_field_widget_info_alter
(
&
$info
)
{
// Add another field type to a widget.
$info
[
'widget_machine_name'
][
'field types'
][]
=
'another_field_type'
;
}
This example works in both Drupal 7 and Drupal 8. The only difference
is that the widget machine name comes from hook_field_widget_info()
(return
value array key) in Drupal
7, and the plugin annotation (id
annotation key) in Drupal 8.
You may also need to alter the widget form so that the widget will work
correctly with the new field type. There are two “form alter” hooks that you can
use for this: hook_field_widget_form_alter()
, which gets called for all widget
forms, and the more specific hook_field_widget_WIDGET_TYPE_form_alter()
, which
gets called only for the widget you are interested in (and is therefore
preferable). These hooks are present in both Drupal 7 and 8.
Further reading and reference:
-
https://api.drupal.org is the best place to look up details of any of the hooks mentioned here.
Programming with Field Formatters
There are two reasons you might need to do some programming with field formatters:
-
If you have defined your own custom field type, you will need to define a formatter that displays the data for that field, or repurpose an existing field formatter.
-
You may need to define a custom formatting method for an existing field type.
If you need to
repurpose an existing field formatter for a different field type, use
hook_field_formatter_info_alter()
, which works the same as
hook_field_widget_info_alter()
described in the preceding section. The
following sections detail how to define new field formatters in Drupal 7 and
8.
Defining a field formatter in Drupal 7
To define a field formatter in Drupal 7, you need to implement two hooks in your
mymodule.module file: hook_field_formatter_info()
and
hook_field_formatter_view()
. If you’re defining a field formatter for a
custom field type that you’ve defined, I suggest going back to the field type
module you used as a starting point and using that module’s formatter
as a starting point for your formatter.
If you’re defining a new formatter for an existing field, the following example may be helpful. Assume that you have set up a Text field with several preselected values, and on output you want to display an icon or some predefined text that corresponds to the preselected value.
Here are the hook implementations for this formatter example:
// Provide information about the formatter.
function
mymodule_field_formatter_info
()
{
return
array
(
// Machine name of the formatter.
'mymodule_myformatter'
=>
array
(
// Label for the administrative UI.
'label'
=>
t
(
'Custom text output'
),
// Field types it supports.
'field types'
=>
array
(
'text'
),
),
// Define additional formatters here.
);
}
// Define how the field information is displayed.
// Return a render array.
function
mymodule_field_formatter_view
(
$entity_type
,
$entity
,
$field
,
$instance
,
$langcode
,
$items
,
$display
)
{
$output
=
array
();
// Verify the formatter type.
if
(
$display
[
'type'
]
==
'mymodule_myformatter'
)
{
// Handle multi-valued fields.
foreach
(
$items
as
$delta
=>
$item
)
{
// See which option was selected.
switch
(
$item
[
'value'
])
{
case
'x_stored'
:
// Output the corresponding text or icon.
$output
[
$delta
]
=
array
(
'#markup'
=>
'<p>'
.
t
(
'Predefined output text x'
)
.
'</p>'
);
break
;
case
'y_stored'
:
// Output the corresponding text or icon.
$output
[
$delta
]
=
array
(
'#markup'
=>
'<p>'
.
t
(
'Predefined output text y'
)
.
'</p>'
);
break
;
// Handle other options here.
}
}
}
return
$output
;
}
Further reading and reference:
-
Render arrays: “Creating Render Arrays for Page and Block Output”
Examples—field formatters:
-
There are many Drupal core examples of field formatters. You can find the core implementations of
hook_field_formatter_info()
on the hook page on https://api.drupal.org. -
A contributed module that I wrote, Simple Google Maps is another example to look at. It’s a formatter for a plain text field, which assumes the text is an address and formats it as an embedded Google map.
-
The Field example in Examples for Developers is also good.
Defining a field formatter in Drupal 8
In Drupal 8, field formatters are plugins. So, to define one in a module, you need to:
-
Define a class in the
Plugin\Field\FieldFormatter
namespace under your module’s namespace, therefore located in the src/Plugin/Field/FieldFormatter directory. The class needs to implement\Drupal\Core\Field\FormatterInterface
, and it typically extends\Drupal\Core\Field\FormatterBase
. You’ll need to define methodviewElements()
(which builds a render array for the output), and you may need to override additional default methods on the base class. -
Annotate it with
\Drupal\Core\Field\Annotation\FieldFormatter
annotation.
If you’re defining a field formatter for a custom field type that you’ve defined, I suggest going back to the field type module you used as a starting point and using that module’s formatter as a starting point for your formatter.
If you’re defining a new formatter for an existing field, the following example may be helpful. Assume that you have set up a plain Text field with several preselected values, and on output you want to display an icon or some predefined text that corresponds to the preselected value.
Here is the plugin class (which goes in file src/Plugin/Field/FieldFormatter/MyCustomText.php under the main module directory):
namespace
Drupal\mymodule\Plugin\Field\FieldFormatter
;
use
Drupal\Core\Field\FormatterBase
;
use
Drupal\Core\Field\FieldItemListInterface
;
/**
* Custom text field formatter.
*
* @FieldFormatter(
* id = "mymodule_myformatter",
* label = @Translation("Custom text output"),
* field_types = {
* "string",
* }
* )
*/
class
MyCustomText
extends
FormatterBase
{
public
function
viewElements
(
FieldItemListInterface
$items
)
{
$output
=
array
();
foreach
(
$items
as
$delta
=>
$item
)
{
// See which option was selected.
switch
(
$item
->
value
)
{
case
'x_stored'
:
// Output the corresponding text or icon.
$output
[
$delta
]
=
array
(
'#markup'
=>
'<p>'
.
$this
->
t
(
'Predefined output text x'
)
.
'</p>'
);
break
;
case
'y_stored'
:
// Output the corresponding text or icon.
$output
[
$delta
]
=
array
(
'#markup'
=>
'<p>'
.
$this
->
t
(
'Predefined output text y'
)
.
'</p>'
);
break
;
// Handle other options here.
}
}
return
$output
;
}
}
Further reading and reference:
Examples—field formatters:
-
There are many good examples of field formatters in Drupal core. You can find them listed on https://api.drupal.org on the page for the
FieldFormatter
annotation class.
Creating Views Module Add-Ons
The Views module (a contributed module in Drupal 7 and part of Drupal core in Drupal 8) is, at its heart, a query engine for Drupal that can be used to make formatted lists of pretty much any type of data. The base Views module and other contributed Views add-on modules provide the ability to query Node module content items, comments, taxonomy terms, users, and other data; to filter and sort the data in various ways; to relate one type of data to another; and to display the data using a list, grid, table, map, and other formats. In addition, custom entities and fields that you have defined are well supported by Views, and Views uses the Field system’s formatters to display field data. But even with all of that ability, you may sometime have needs not covered by Views and existing add-on modules.
This section provides an overview of how to create your own Views module add-ons for the following purposes:
-
Querying additional types of data
-
Relating new data to existing data types
-
Formatting the output in additional ways
-
Providing default views that site builders can use directly or adapt to their needs
Aside from the background information in “Views Programming Terminology and Output Construction”, which you should read or at least skim, each topic in this section is independent. However, if you are programming in Drupal 7, you’ll need to follow the steps in “Setting Up Your Module for Views in Drupal 7” in order to accomplish all other Views tasks. Also note that some of the topics in this section assume knowledge of advanced usage of the Views user interface, such as relationships and contextual filters.
Further reading and reference:
-
Views module for Drupal 7: https://www.drupal.org/project/views.
-
If you use the Views Bulk Operations module and you need a custom bulk operation: they are actually Rules components. You can create these in the Rules user interface; see also “Creating Rules Module Add-Ons in Drupal 7”.
-
For Views programming not covered in this book, there is documentation in the views.api.php file in the main Views module download for Drupal 7, as well as in the Advanced Help provided by the module. For Drupal 8, the API is documented in a series of topics that you can find on https://api.drupal.org—start with the “Views overview” topic and go from there.
Views Programming Terminology and Output Construction
There are several pieces of background knowledge you’ll need before you start programming for the Views module.
First, some terminology. Views makes use of a number of PHP classes, which in the Drupal 7 version are separated into several groups:
-
Classes known as handlers take care of field display, sorting, filtering, contextual filtering, and relationships.
-
Classes known as plugins take care of the overall display of the view, access restrictions, paging, and default values for contextual filters.
-
Other classes that are neither plugins nor handlers take care of assembling and performing the database query and other Views functionality.
The distinction among handlers, plugins, and other classes is somewhat arbitrary, but because they’re declared and defined differently, it’s important to know about if you are programming for Drupal 7. In Drupal 8, the distinction among these types of classes doesn’t hold—nearly all of them are plugins in Drupal 8, using the standard Drupal 8 plugin API; however, you’ll still see some of these plugins referred to as handlers in Drupal 8 documentation.
Warning
Some Views code and documentation use the term argument for what is called a contextual filter in the Drupal 7 and 8 user interface (in Drupal 6 and prior versions of Views, it was also called an argument in the user interface). The terms are basically interchangeable in Views.
In order to program effectively with Views, you also need to understand how Views uses handlers and plugins to construct its output. Here is a conceptual overview (the actual order of Views performing these steps may be a bit different):
-
Views takes all of the field, relationship, filter, contextual filter, and sort definitions in the view and creates and executes a database query. This process involves a number of handlers, such as relationship handlers, field handlers, filter handlers, and sort handlers. Fields defined using the Drupal Field API are not added directly to the query unless they are used in relationships, sorts, or filters; they are instead loaded during the display process.
-
If the view uses fields in its display, each field is run through its field handler’s display routines to render the fields.
-
Each row in the database query result is run through a row plugin (known as row style plugin in Drupal 7), if one is in use. Row plugins format the rows, as a combination of rendered fields and/or raw query output.
-
The formatted rows and/or fields are handed off to the style plugin, which combines the rows and fields into a larger output. The base Views module includes style plugins for HTML tables, HTML unordered lists, and so on, and each style plugin is compatible with a certain subset of row plugins (for instance, an HTML list can use either a field row plugin or a row plugin that displays the entire entity, whereas an HTML table does not use a row plugin).
-
The formatted output is handed off to the overall display plugin; examples of display plugins are the standard Views Page, Block, and Feed displays.
Note that Views caches information about what hooks, handlers, and plugins exist (and their properties), so whenever you add or modify the properties of a Views hook implementation, handler class, or plugin class, you will most likely need to clear the Views cache. You can do this on the Views advanced settings page, where you can also disable Views caching while you’re developing. The Views cache is also cleared when you clear the main Drupal cache.
Further reading and reference:
Setting Up Your Module for Views in Drupal 7
In Drupal 7, several steps are necessary to make sure that Views will
recognize your module as a valid provider of default views, handlers, and/or
plugins; none of these steps
is needed for Drupal 8 Views programming. The first step for Drupal 7
is to implement the Views hook_views_api()
hook in your
mymodule.module file. To do that, you’ll need to choose
a location for some additional files; typically, you make a subdirectory called
views in your module directory to hold all of the Views files, and if you
are doing a lot of output formatting, optionally another subdirectory for the
theme template files. Alternatively, you can just put all the Views files
in your main module directory.
The hook_views_api()
implementation tells Views this
information. For example:
function
mymodule_views_api
()
{
return
array
(
// Which version of the Views API you are using. For Views 7.x-3.x, use 3.
'api'
=>
3
,
// Where your additional Views directory is, if you have one.
'path'
=>
drupal_get_path
(
'module'
,
'mymodule'
)
.
'/views'
,
// Where Views-related theme templates are located.
'template path'
=>
drupal_get_path
(
'module'
,
'mymodule'
)
.
'/views/templates'
,
);
}
Any files that contain Views classes (see the following sections) will also need to be added to your mymodule.info file, so that they are recognized by the Drupal 7 class loader:
files[] = views/name_of_my_include_file.inc
In addition, if Views integration is fundamental to the functioning of your module, you can make Views a module requirement by adding the following line to your mymodule.info file:
dependencies[] = views
Further reading and reference:
Providing a New Views Data Source
A common need in a custom module is to integrate it with Views—that is, to make the data managed by the module available to Views. If you are storing data in existing entities or using standard Drupal fields to store the data, your data will already be integrated with Views. But if you are defining your own entity type in Drupal 7, or for some reason not using entities, you will need to provide Views integration yourself, by defining a new Views data source (also known as a base table). Once you’ve defined the data source, you can select it when setting up a new view: instead of selecting a Node-module Content view (the default), you can select your data source instead, or if appropriate, you can create a view using a different data type, and use a relationship to join it with your data type. Adding data sources is described in this section; the next section describes how to add fields and relationships to existing data sources.
Drupal 8
You can provide Views integration for non-entity data in Drupal 8 by using
hook_views_data()
, which is very similar to the Drupal 7 hook described
here. This is not common, as most data in Drupal 8 should be stored in
entities. Views integration for content entities in Drupal 8 is described in
“Defining a Content Entity Type in Drupal 8”.
To define a Views data source in Drupal 7, assuming you have
already followed the steps in “Setting Up Your Module for Views in Drupal 7”,
start by creating a file called
mymodule.views.inc, which must be located in the Views directory
specified in your hook_views_api()
implementation. In Drupal 8, the
mymodule.views.inc file is located in the top-level module directory.
In this file, implement hook_views_data()
.
The return value of this hook, in both versions of Drupal, is an associative
array of arrays, where the outermost array key is the database table name, and
the array value gives information about that database table, the way it relates
to other data tables known to Views, and the database table fields that can be
used for filtering, sorting, and field display.
Here is an example, showing a subset of the return value of
this hook in Drupal 7 for the User module (function user_views_data()
, located
in the modules/user.views.inc file under the main Views directory). The code has
been slightly modified for clarity, and comments have been added (including
notes about how you would do something similar in Drupal 8, which is mostly the
same except where noted):
// The main data table is 'users'.
$data
[
'users'
]
=
array
();
// The 'table' section gives information about the table as a whole.
$data
[
'users'
][
'table'
]
=
array
();
// Grouping name for fields in this table in the Views UI.
$data
[
'users'
][
'table'
][
'group'
]
=
t
(
'User'
);
// Define this as a base table, meaning it can be used as the starting
// point for a view.
$data
[
'users'
][
'table'
][
'base'
]
=
array
(
// Primary key field.
'field'
=>
'uid'
,
'title'
=>
t
(
'User'
),
'help'
=>
t
(
'Users who have created accounts on your site.'
),
'access query tag'
=>
'user_access'
,
);
// Tell Views this is an entity.
$data
[
'users'
][
'table'
][
'entity type'
]
=
'user'
;
// Define individual database table fields for use as Views fields,
// filters, sorts, and arguments (also known as contextual filters).
// The 'uid' database field.
$data
[
'users'
][
'uid'
]
=
array
(
// Overall title and help, for all uses, except where overridden.
'title'
=>
t
(
'Uid'
),
'help'
=>
t
(
'The user ID'
),
// Expose it as a views Field.
'field'
=>
array
(
// Name of the field handler class to use, for Drupal 7.
'handler'
=>
'views_handler_field_user'
,
// In Drupal 8, replace this with the ID of the field plugin:
// 'id' => 'field',
// Override a setting on the field handler.
'click sortable'
=>
TRUE
,
),
// Expose it as a views Contextual Filter (argument).
'argument'
=>
array
(
// Name of argument handler class to use, for Drupal 7.
'handler'
=>
'views_handler_argument_user_uid'
,
// In Drupal 8, replace this with the ID of the argument plugin:
// 'id' => 'user_uid',
// Override a setting.
'name field'
=>
'name'
,
),
// Expose it as a views Filter.
'filter'
=>
array
(
// Call the filter 'Name' instead of 'Uid' in the UI.
'title'
=>
t
(
'Name'
),
// Name of filter handler class to use, for Drupal 7.
'handler'
=>
'views_handler_filter_user_name'
,
// In Drupal 8, replace this with the ID of the filter plugin:
// 'id' => 'user_name',
),
// Expose it as a views Sort.
'sort'
=>
array
(
// Name of sort handler class to use, for Drupal 7.
'handler'
=>
'views_handler_sort'
,
// In Drupal 8, replace this with the ID of the sort plugin:
// 'id' => 'standard',
),
// Define a relationship (join) that can be added to a view of Users,
// where this field can join to another base Views data table. Only
// one join per base table field can be defined; to define more, you
// will need to use dummy field entries in the base table.
'relationship'
=>
array
(
// Call the relationship 'Content authored' instead of 'Uid'.
'title'
=>
t
(
'Content authored'
),
// Also override the 'uid' default help.
'help'
=>
t
(
'Relate content to the user who created it.'
),
// Name of relationship handler class to use, for Drupal 7.
'handler'
=>
'views_handler_relationship'
,
// In Drupal 8, replace this with the ID of the relationship plugin:
// 'id' => 'standard',
// Name of Views base table to join to.
'base'
=>
'node'
,
// Database table field name in the joined table.
'base field'
=>
'uid'
,
// Database table field name in this table.
'field'
=>
'uid'
,
// When you add a relationship in the UI, you assign it a label, which
// is used elsewhere in the UI. This provides the default value.
'label'
=>
t
(
'nodes'
),
),
);
Further reading and reference:
Adding Handlers to Views
A hook_views_data()
implementation in Drupal 7 refers to the names of handler
classes in various spots (fields, filters,
sorts, etc.). In Drupal 8, your hook_views_data()
or entity views data class
instead refers to the IDs of the handler classes.
In either case, you have the choice to use handlers provided by the Views
module, or a class you create.
To create your own handler class in Drupal 7, here are the steps:
-
Usually, create a handlers subdirectory inside the Views directory specified in your
hook_views_api()
implementation. -
In that subdirectory, create an include file named for your handler class, such as mymodule_views_handler_field_myfield.inc if your class is called
mymodule_views_handler_field_myfield
. Note that in contrast with the usual Drupal coding standards, for historical reasons Views-related classes are generally defined using all-lowercase names with underscores, rather than CamelCase names. -
In that file, extend an existing Views handler class of the same type (field, filter, etc.), and override the appropriate methods to define the actions of your class. You can find existing Views handlers in the handlers subdirectory of the Views download. For instance, if you are making a field handler, you’ll need to extend the
views_handler_field
class and override therender()
method; if your field handler has display options, you’ll also need to override theoption_definition()
andoptions_form()
methods. -
Add the handler file to your mymodule.info file, so that the class will be automatically loaded by the Drupal class-loading system:
files[] = views/handlers/mymodule_views_handler_field_myfield.inc
In Drupal 8, handlers use the Plugin API; see “The Basics of Drupal 8 Plugin Programming” for details on how to define them. The information you’ll need:
- Namespace
-
The different types of handlers each have their own namespace. For instance, field handlers go in the
Plugin\views\field
namespace, and contextual filter (argument, in code) plugins go inPlugin\views\argument
, under your main module namespace; therefore, in the src/Plugin/views/field and src/Plugin/views/argument subdirectories under your main module directory. - Annotation
-
Each of these types of handlers has an annotation class, such as
\Drupal\views\Annotation\ViewsField
and\Drupal\views\Annotation\ViewsArgument
. - Base classes
-
Each type of handler has a base class, such as
\Drupal\views\Plugin\views\field\FieldPluginBase
, or you might want to extend one of the existing handler plugins in the appropriate core/modules/views/src/Plugin/views/ subdirectory.
Further reading and reference:
-
“Providing a New Views Data Source” or in Drupal 8 for entities, “Defining a Content Entity Type in Drupal 8”
Examples—handlers:
-
In Drupal 7, the Views module’s handlers directory contains general-purpose handlers. Use these as starting points when defining your own handlers. The Drupal 7 API module also has some good examples of handler classes.
-
In Drupal 8, the Views module’s handlers are in subdirectories of core/modules/views/src/Plugin/views in Drupal core. Some entity modules also have their own plugins in their src/Plugin/views directories.
Adding Fields and Relationships to an Existing Views Data Source
In addition to providing completely new Views data sources, as described in
“Providing a New Views Data Source”,
some custom modules may need to provide additional fields or
relationships to existing Views data sources. To do this, implement
hook_views_data_alter()
in the mymodule.views.inc file that you set up in
the previous section; this hook takes as input, by reference, the array of all
of the hook_views_data()
information from all implementing modules and allows
you to alter it. Note that in Drupal 8 you would use hook_views_data_alter()
to alter entity views data as well as non-entity views data.
This example from the Drupal 7 API module illustrates the two most common things you can do with this hook:
-
Adding a relationship from an existing table to your table: in this example, the reason is that the API module allows users to comment on API documentation pages, so if someone were creating a view whose base data source is comments, they might want to add a relationship to the API documentation page that is being commented upon. Relationships are defined on the base table side, so this relationship needs to be added to the comment table’s Views data.
-
Adding an automatic join to your table (automatic joins provide additional database fields to a data source without having to add a relationship to the view): again, this example is comment-related: the
node_comment_statistics
table is normally automatically joined to thenode
base table, so that the number-of-comments field is available on node content items. Automatic joins are defined on the table that is automatically joined to a Views base table, so this join needs to be added to thenode_comment_statistics
table Views data, to automatically join it when theapi_documentation
table is in a view.
Here is the code for Drupal 7 to make these two modifications, with notes added where Drupal 8 would be different:
function
api_views_data_alter
(
&
$data
)
{
// Add a relationship to the Comment table. The array key must be
// unique within the comment table -- do not overwrite any existing
// comment fields.
$data
[
'comment'
][
'did'
]
=
array
(
'title'
=>
t
(
'Documentation ID'
),
'help'
=>
t
(
'The ID of the documentation object the comment is a reply to.'
),
'relationship'
=>
array
(
// Table to join to.
'base'
=>
'api_documentation'
,
// Field in that table to join with.
'base field'
=>
'did'
,
// Field in the comment table to join with.
'field'
=>
'nid'
,
// Name of relationship handler class to use for Drupal 7.
'handler'
=>
'views_handler_relationship'
,
// For Drupal 8, this would be the ID of the relationship plugin, like:
// 'id' => 'standard',
// When you add a relationship in the UI, you assign it a label, which
// is used elsewhere in the UI. This provides the default value.
'label'
=>
t
(
'API documentation object'
),
'title'
=>
t
(
'API documentation object'
),
'help'
=>
t
(
'The ID of the documentation object the comment is a reply to.'
),
),
);
// Add an automatic join between the comment statistics table and
// the API documentation table.
$data
[
'node_comment_statistics'
][
'table'
][
'join'
][
'api_documentation'
]
=
array
(
// Use an inner join.
'type'
=>
'INNER'
,
// Field to join on in the API documentation table.
'left_field'
=>
'did'
,
// Field to join on in the comment statistics table.
'field'
=>
'nid'
,
);
}
Providing a Style or Row Plugin to Views
Another common custom Views programming need is to create new style or row plugins.
In Drupal 8, style plugins and row plugins use the Plugin API; see “The Basics of Drupal 8 Plugin Programming” for details on how to define them. The information you’ll need:
- Namespace
-
Style plugins go in the
Plugin\views\style
namespace, and row plugins inPlugin\views\row
, under your main module namespace; therefore, in the src/Plugin/views/style and src/Plugin/views/row subdirectories under your main module directory. - Annotation
-
Style plugins have
\Drupal\views\Annotation\ViewsStyle
annotation, and row plugins have\Drupal\views\Annotation\ViewsRow
annotation. - Base classes
-
The base class for style plugins is
\Drupal\views\Plugin\views\style\StylePluginBase
, or you might want to extend one of the existing style plugins in core/modules/views/src/Plugin/views/style. The base class for row plugins is\Drupal\views\Plugin\views\row\RowPluginBase
, or you might want to extend one of the existing row plugins in core/modules/views/src/Plugin/views/row. - Theming
-
Style plugin annotation refers to a theme hook, and row plugins can also set up render arrays with new theme hooks. You’ll need to define your theme hooks using
hook_theme()
.
In Drupal 7, row and style plugins are detected by Views using a hook, so the process is a bit more complicated. Here are the steps to follow, assuming you have already followed the steps in “Setting Up Your Module for Views in Drupal 7”:
-
Implement
hook_views_plugins()
in your mymodule.views.inc file, which must be located in the Views directory specified in yourhook_views_api()
implementation. The return value tells Views about your style and row style plugin classes. For instance, you might have:
function
mymodule_views_plugins
()
{
return
array
(
// Overall style plugins
'style'
=>
array
(
// First style plugin--machine name is the array key.
'mymodule_mystyle'
=>
array
(
// Information about this plugin.
'title'
=>
t
(
'My module my style'
),
'help'
=>
t
(
'Longer description goes here'
),
// The class for this plugin and where to find it.
'handler'
=>
'mymodule_views_plugin_style_mystyle'
,
'path'
=>
drupal_get_path
(
'module'
,
'mymodule'
)
.
'/views/plugins'
,
// Some settings.
'uses row plugin'
=>
TRUE
,
'uses fields'
=>
TRUE
,
),
// Additional style plugins go here.
),
// Row style plugins.
'row'
=>
array
(
// First row style plugin -- machine name is the array key.
'mymodule_myrowstyle'
=>
array
(
// Information about this plugin.
'title'
=>
t
(
'My module my row style'
),
'help'
=>
t
(
'Longer description goes here'
),
// The class for this plugin and where to find it.
'handler'
=>
'mymodule_views_plugin_row_myrowstyle'
,
'path'
=>
drupal_get_path
(
'module'
,
'mymodule'
)
.
'/views/plugins'
,
// Some settings.
'uses fields'
=>
TRUE
,
),
// Additional row style plugins go here.
),
);
}
-
Create a file for each style or row style plugin class. For example, if you declared that your class is called
mymodule_views_plugin_style_mystyle
, create a file with the name mymodule_views_plugin_style_mystyle.inc. Put this file in the directory you specified in yourhook_views_plugins()
implementation (typically, plugins are either put into your Views directory or a subdirectory called plugins). -
List each class-containing include file in your mymodule.info file, so that the class will be automatically loaded by the Drupal class-loading system, with a line like:
files[] = views/plugins/mymodule_views_plugin_style_mystyle.inc
-
In each class-containing include file, declare your plugin class, which should extend either the
views_plugin_style
,views_plugin_row
, or another subclass of these classes. You will need to override theoption_definition()
andoptions_form()
methods if your plugin has options, and (oddly enough) that is usually all you’ll need to override, because the work of formatting the output is done in the theme layer. -
Set up
hook_theme()
to define a theme template and preprocessing function for your plugin. The theme template goes into the template directory specified in yourhook_views_info()
implementation, and the name corresponds to the machine name you gave your plugin (in this example, mymodule-mystyle.tpl.php or mymodule-myrowstyle.tpl.php).
Further reading and reference:
Examples—plugin classes:
-
The Views module has several general-purpose plugins, which are good starting points and examples. In Drupal 7, the
hook_views_plugins()
implementation is in the includes/plugins.inc file, plugin class files are in the plugins directory, and template files are in the theme directory, with theme-preprocessing functions in the theme/theme.inc file. In Drupal 8, plugins are in subdirectories of core/modules/views/src/Plugin/views in Drupal core. -
There are several contributed module projects that provide Views plugin add-ons (they may not yet be ported to Drupal 8). Commonly used examples are Views Data Export, Calendar, and Views Slideshow. You can find others by browsing the “Views” category at https://www.drupal.org/project/modules (but note that only some of the Views-related modules in that list provide style or row plugins).
Providing Default Views
Once you have your module’s data integrated with Views—either because it is stored in entities using the Entity API module in Drupal 7, core entities, or fields or because you have provided a custom data source as described in the preceding sections—you may want to supply users of your module with one or more default views. These views can be used to provide administration pages for your module or sample output pages, and they can either be enabled by default or disabled by default (administrators can enable and modify them as needed).
Here are the steps to follow to provide one or more default views in your Drupal 7 module, assuming you have already followed the steps in “Setting Up Your Module for Views in Drupal 7”:
-
Create a view using the Views user interface.
-
From the Views user interface, export the view. This will give you some PHP code starting with
$view = new view;
. -
If you want to have the view disabled by default, find the line near the top that says
$view->disabled = FALSE;
and changeFALSE
toTRUE
. -
Implement
hook_views_default_views()
in a file called mymodule.views_default.inc, which must be located in the Views directory specified in yourhook_views_api()
implementation. -
Put the exported view’s PHP code into this hook implementation:
function
mymodule_views_default_views
()
{
// Return this array at the end.
$views
=
array
();
// Exported view code starts here.
$view
=
new
view
;
// ... rest of exported code ...
// Exported code ends here.
// Add this view to the return array.
$views
[
$view
->
name
]
=
$view
;
// You can add additional exported views here.
return
$views
;
}
In Drupal 8, views are configuration. So, to provide a default view in a module, here are the steps:
-
Create the view in the Views user interface.
-
Export the configuration to a file. You can do this on the configuration export page in the administrative UI (example.com/admin/config/development/configuration/single/export), which tells you the filename to save it as.
-
Remove the UUID line near the top from the exported file. This is part of your site configuration, but it should not be set in configuration for other sites to import.
-
Put this file in your module’s config/install or config/optional directory.
Creating Rules Module Add-Ons in Drupal 7
The contributed Rules module lets you set up reaction rules, which are a set of actions that are executed in response to events under certain conditions on your website. For example, you could respond to a new comment submission event under the condition that the submitter is an anonymous user, by sending the comment moderator an email message. The configuration offered by the Rules user interface is quite flexible and powerful:
-
You can define the events, conditions, and actions for a reaction rule in the Views user interface, without any programming.
-
Conditions can be combined using Boolean AND/OR logic.
-
Actions can have parameter inputs and can provide data outputs, so you can chain actions together, with the output data provided by one action feeding in as a parameter for the next action.
-
Some actions provide arrays as output, and Rules has a special action that lets you loop over an array, doing one or more actions on each array element.
-
You can also set up components, which are basically reusable subsets of reaction rules. Components can have inputs and outputs, as well as their own events, conditions, and/or actions.
-
Once you have created a component, you can use it like an action in building a reaction rule.
-
Components can also be used as bulk operations in the Views Bulk Operations module, if they take an entity or a list of entities as their first input value.
The Rules module comes with a set of standard events, conditions, and actions, including many related to entities and fields (in Drupal 7, these require the contributed Entity API module). This means that if your module stores its custom data in entities and fields, you will be able to use the Rules module with your module’s data without any further programming. But you may occasionally find that you need to do some programming to add additional functionality to the Rules module; in my experience, this has always been to add custom actions to Rules; this is described in “Providing Custom Actions to Rules”.
In Drupal 7, reaction rules and components that you compose using the Rules user interface can be exported into PHP code and shared with others. One way to do this is by using the Features contributed module. But sometimes Features is cumbersome, and there is a direct method for exporting and sharing reaction rules and components described in “Providing Default Reaction Rules and Components”.
Drupal 8
As of September 2015, the API for Rules in Drupal 8 has not been finalized, but it will definitely be quite different from the Drupal 7 API. The following sections only apply to Drupal 7.
Further reading and reference:
-
Rules module: https://www.drupal.org/project/rules.
-
Features module: https://www.drupal.org/project/features.
-
Views Bulk Operations module: https://www.drupal.org/project/views_bulk_operations.
-
For programming with Rules not covered in this book, see the rules.api.php file distributed with the Rules module for documentation. For instance, it is possible to set up custom conditions and events, although it is unlikely you will ever need to, given the flexibility of the base Rules module and its entity integration.
Providing Custom Actions to Rules
Rules actions are responses to events and conditions detected by the Rules module, and they can take many forms. Built-in actions that come with the Rules module include sending an email message, displaying a message or warning, and altering content (publishing, unpublishing, etc.). As mentioned in the introduction to this section, you can chain together the input and output of several actions and you can also use action output for looping, so some so-called actions are really more like processing steps that exist solely to provide input for other actions that are actually doing the work (modifying content, sending email, etc.).
Whether you are defining a processing step type of action or one that actually does work itself, here are the steps you will need to follow to provide a custom action to the Rules module in Drupal 7:
-
Create a file called mymodule.rules.inc in your main module directory, and implement
hook_rules_action_info()
in that file. The return value tells Rules about your custom action: its machine name, a human-readable label for the Rules user interface, the data that it requires as parameters (if any), and the data that it provides as output (if any). -
Create a callback function that executes your action. You can either put this function in your mymodule.rules.inc file, or you can implement
hook_rules_file_info()
and specify a separate include file for callbacks. The name of the function is the same as the machine name you gave the action.
As an example, here is the code to provide a processing-step-type action that takes a content item as input and outputs a list of users (you could then loop over the output list and send each user an email message, for instance):
// Optional hook_rules_file_info() implementation.
// This specifies a separate file for callback functions.
// It goes into mymodule.rules.inc.
function
mymodule_rules_file_info
()
{
// Leave off the .inc filename suffix.
return
array
(
'mymodule.rules-callbacks'
);
}
// Required hook_rules_action_info() implementation.
// This gives information about your action.
// It goes into mymodule.rules.inc.
function
mymodule_rules_action_info
()
{
$actions
=
array
();
// Define one action.
// The array key is the machine name of the action, and also the
// name of the function that does the action.
$actions
[
'mymodule_rules_action_user_list'
]
=
array
(
// Label and group in the user interface.
'label'
=>
t
(
'Load a list of users related to content'
),
'group'
=>
t
(
'My Module custom'
),
// Describe the parameters.
'parameter'
=>
array
(
'item'
=>
array
(
'label'
=>
t
(
'Content item to use'
),
'type'
=>
'node'
,
),
// You can add additional parameters here.
),
// Describe the output.
'provides'
=>
array
(
'user_list'
=>
array
(
'type'
=>
'list<user>'
,
'label'
=>
t
(
'List of users related to content'
),
),
// You could describe additional output here.
),
);
// Define other actions here.
return
$actions
;
}
// Required callback function that performs the action.
// This goes in mymodule.rules.inc, or the file defined in
// the optional hook_rules_file_info() implementation.
function
mymodule_rules_action_user_list
(
$item
)
{
// Because the parameter defined for this action is a node,
// $item is a node. Do a query here to find a list of
// users related to this node.
// As a proxy for your real code, return a list of one
// user -- the author of the content.
$ids
=
array
(
$item
->
uid
);
// Load the users and return them to Rules.
return
array
(
'user_list'
=>
user_load_multiple
(
$ids
));
}
Providing Default Reaction Rules and Components
In some cases, you may find that you want to put reaction rules or components that you have created into PHP code, so that you can use them on another site. You have three choices for how to do this in Drupal 7:
-
Define the reaction rule or component’s events, conditions, and reactions using pure PHP code. This is somewhat documented in the rules.api.php file distributed with the Rules module, but it is not particularly recommended, as you’ll need to read a lot of Rules module code to figure out the machine names of all the pieces, and there isn’t really any documentation on how to put it all together.
-
Create the reaction rule or component using the Rules user interface, and use the contributed Features module to manage the export.
-
Create the reaction rule or component using the Rules user interface, export the definition to a text file, and implement a Rules hook to provide it as an in-code rule or component. This process is recommended if you do not want to use the Features module, and is described here.
Assuming you want to use the export-to-text option, here are the steps to follow:
-
In the Rules user interface, create your reaction rule or component. If you do not want a reaction rule to be active by default, be sure to deactivate it.
-
From the Rules user interface, export your reaction rule or component, and save the exported text in a file. Put this file in a rules subdirectory of your main module directory, and name it sample_rule.txt (for example).
-
Implement
hook_default_rules_configuration()
in a file named mymodule.rules_defaults.inc, with the following code:
function
mymodule_default_rules_configuration
()
{
$configs
=
array
();
// Read in one exported reaction rule.
$file
=
drupal_get_path
(
'module'
,
'mymodule'
)
.
'/rules/sample_rule.txt'
;
$contents
=
file_get_contents
(
$file
);
$configs
[
'mymodule_sample_rule'
]
=
rules_import
(
$contents
);
// Add other reaction rules and components here if desired.
return
$configs
;
}
Programming with CTools in Drupal 7
The contributed Chaos Tools (CTools) module is a suite of APIs and tools designed to be used by other modules, including a generic plugin system, a system for packaging configuration data for export into code, a context system for detecting conditions involving the site and its data, and other components. If you are writing a module for Drupal, look through the contents of CTools, and you may find that you can use its tools rather than writing your own code for some of the basic tasks your module needs to do.
Implementing CTools Plugins for Panels
The contributed Panels module, which is based heavily on CTools, allows you to set up custom page layouts, which are managed using the CTools page manager tool. Panels also uses the CTools context system, which allows the content displayed in the panel layout’s regions to respond to the URL path, properties of content being displayed, the logged-in user’s role, and other conditions. Because Panels uses the CTools plugin system for almost all of its functionality, you can extend and alter the functionality of Panels by creating your own plugins (this process is known as implementing plugins, to distinguish it from the process of defining plugin types).
Drupal 8
The plugin system defined by the CTools module for Drupal 7 was not adopted for Drupal 8, which has its own plugin system in Drupal core. Thus, the information about CTools plugins provided here is only applicable to Drupal 7. As of September 2015, the Panels module has not been finalized for Drupal 8, but it will likely be using the Drupal core plugin system rather than the CTools plugin system described here.
This section describes the steps in implementing a CTools plugin for Drupal 7. The example used is a plugin implementation that adds a custom relationship to the CTools context system, for use in Panels. Specifically, the example plugin implementation provides a relationship from a user to the most recent node content item the user has authored. The steps to implement this plugin are described in the following sections.
Warning
Panels relationships are not the same as Views relationships. Also, although in Drupal 7 the Views module depends on the CTools module, Views does not use the CTools plugin system for defining and detecting its plugin and handler types. See “Creating Views Module Add-Ons” instead if you are interested in Views.
Further reading and reference:
-
Panels module: https://www.drupal.org/project/panels
-
Chaos Tools (CTools) module: https://www.drupal.org/project/ctools
-
Both Panels and CTools have help available on their API, if you install the Advanced Help module, and CTools has a ctools.api.php file with hook documentation. The Advanced Help topics in Panels and CTools also describe how to use the Panels user interface to create panel pages and use contexts and relationships.
Determining plugin background information
A CTools plugin implementation consists of an array of definition data and usually one or more callback functions mentioned in the definition array. Plugins come in many varieties, known as types, and each plugin type has a specific format for its definition array. So, before implementing a plugin in your module, you need to locate several pieces of background information about the plugin type:
-
Whether the functionality you want to define can even be provided by implementing a CTools plugin
-
If so, which module defines the plugin type that provides this functionality
-
The machine name of the plugin type
-
The elements of the definition array that plugins of this type need to define
-
The callback functions that plugins of this type need to define, and their signatures
-
Whether there are any restrictions on the machine name you will choose for your plugin
Unfortunately, modules that define plugin types do not have a uniform way of providing this information to Drupal programmers, so the rest of this section describes some steps you can go through to locate the information you’ll need. If you already have this information in hand for the plugin you’re implementing, you can skip the rest of this section.
A good starting point for determining if the functionality is covered by a plugin would be to look for a README.txt file, a *.api.php file, or Advanced Help in the module whose functionality you are trying to add to. In the present example of adding a custom relationship to Panels, the Panels Advanced Help “Working with the Panels API” topic tells you that relationship plugins are part of the context system provided by the CTools module.
The next step is to find the plugin machine name. To do this, you’ll need
to locate
the implementation of hook_ctools_plugin_type()
in the module that
defines the plugin type (this function should be in the main module file). In
this case, because it is a plugin type defined by the CTools module itself, you
are looking for a function called ctools_ctools_plugin_type()
in the
ctools.module file; if you had determined that your plugin type was defined by the
Panels module, you’d be looking for panels_ctools_plugin_type()
in
panels.module. In either case, the return value of the function is an array
of plugin type definitions, where the keys are the machine names of the plugin
types, and the corresponding values are defaults for the plugin definition
arrays.
Unfortunately, the CTools implementation of this hook does not include the definitions directly. Instead, it does this:
ctools_passthrough
(
'ctools'
,
'plugin-type'
,
$items
);
The ctools_passthrough()
utility (in the CTools includes/utility.inc file)
delegates the work to functions called ctools*plugin_type()
in include files
called includes/*plugin-type.inc. Scanning the list of files matching this
pattern in the CTools module download, you can eventually find the plugin type
definition in the ctools_context_plugin_type()
function in CTools the
includes/context.plugin-type.inc file:
$items
[
'relationships'
]
=
array
(
'child plugins'
=>
TRUE
,
);
This plugin type definition tells you two things. First, the machine name of the
plugin type is relationships
(note the final s
!). Second, because the plugin
definition array
is very simple, this plugin type uses the standard CTools implementation methods
described in this section. Most plugins have a simple array like this in their
hook_ctools_plugin_type()
implementation; there are two elements to watch out
for that could be present in a plugin definition array that make slight changes
to the implementation method described here:
extension
-
The plugin definition should be placed in a file with a different extension than the default *.inc.
info file
-
The plugin definition should be provided in .info file format (like a module or theme .info file) instead of in a PHP array. The specifics of plugins using this format are not covered in this book, so you’ll have to look for existing examples instead.
The next step is to figure out what data and callbacks this plugin type expects. Look for this information in a README.txt file, an Advanced Help topic, or in existing plugin implementations from the module that defines the plugin type. For the present example, there is a CTools Advanced Help topic that includes a description of the definition array elements, and there are several relationship plugins in the CTools plugins/relationships directory that you can use as examples.
Finally, you’ll need to choose a machine name for your plugin; this example uses
the
machine name mymodule_relationship_most_recent_content
. As with other Drupal
programming, machine names must be unique, so it is customary to use the
module name as a prefix, followed by a descriptive name. Be careful though: some
types of CTools plugins have implicit (and likely undocumented) machine name
restrictions, because they get stored in a database table field of that
length. So to avoid trouble, it is probably best to pick as short of a name as
possible that is still unique and descriptive. For instance, CTools content
type plugins (which provide content panes you can insert into Panels layouts)
have an implicit machine name length maximum of 32 characters.
Notifying CTools about plugin implementations
With the necessary background information in hand, the next step in creating a CTools plugin is to tell CTools that your module includes plugin implementations, and where to find them. Each plugin goes in its own file, and plugin files that you create must be placed in directories that are specific to the plugin type. The usual convention is to put CTools plugins into the plugins subdirectory under your main module directory, and organize them into subdirectories under that.
To tell CTools where your plugin directory is, implement
hook_ctools_plugin_directory()
in your main mymodule.module file:
function
mymodule_ctools_plugin_directory
(
$module
,
$plugin
)
{
return
'plugins/'
.
$module
.
'-'
.
$plugin
;
}
The function parameters are the machine names of the module and plugin type; the return value is the directory name that you want to use for plugins of that type. You can make your directory structure from these inputs however you wish, as long as each plugin type you implement has its own directory. The example here means that an implementation of a relationship plugin defined by the CTools module goes into the plugins/ctools-relationships subdirectory under the main module directory.
Further reading and reference:
Writing the plugin implementation code
The next step in implementing a CTools plugin is to create a PHP file for your plugin implementation in your plugin directory. The filename is the machine name you chose for your plugin, with a .inc extension (plugins/ctools-relationships/mymodule_relationship_most_recent_content.inc in this example).
The file starts with the definition of the $plugin
array in the global scope,
which contains the definition for your plugin implementation:
$plugin
=
array
(
'title'
=>
t
(
'My Module context relationship plugin'
),
'description'
=>
t
(
'Locates the most recent content item authored by a user'
),
'required context'
=>
new
ctools_context_required
(
t
(
'User'
),
'user'
),
'context'
=>
'mymodule_relationship_most_recent_content'
,
'keyword'
=>
'node'
,
);
Notes:
-
The specific elements of the definition array depend on the type of plugin you are implementing.
-
Many plugin types use
title
anddescription
elements, and these values should be passed through thet()
function so that they are translated for display in the Drupal user interface. -
The
required context
element is specific to CTools context plugins; it tells CTools what the context input is for your plugin implementation. In this example, the one input is the user whose most recent content is to be found. If your plugin takes multiple inputs, you can make this an array. -
The
context
element is the name of the callback function that CTools will call when this relationship is selected as part of a Panels page. -
The
keyword
element is a suggestion for the user interface, which provides the default name for the relationship result.
The final step is to define any callback functions referenced in your definition array. These also go into your include file, and their signatures are specific to the plugin type. This example requires one function:
function
mymodule_relationship_most_recent_content
(
$context
=
NULL
,
$config
)
{
// Read the user ID from the context. If you have multiple context inputs,
// $context will be an array of contexts. But there is only one here.
if
(
empty
(
$context
)
||
empty
(
$context
->
data
)
||
empty
(
$context
->
data
->
uid
))
{
// If there is a problem, return an empty CTools context. This is also
// used by CTools to determine the output data type of this plugin.
return
ctools_context_create_empty
(
'node'
,
NULL
);
}
$uid
=
$context
->
data
->
uid
;
// Locate the most recent content node created by this user.
$nid
=
db_select
(
'node'
,
'n'
)
->
fields
(
'n'
,
array
(
'nid'
))
->
condition
(
'uid'
,
$uid
)
->
orderBy
(
'created'
,
'DESC'
)
->
range
(
0
,
1
)
->
execute
()
->
fetchField
();
// Load the node item if possible.
if
(
!
$nid
)
{
return
ctools_context_create_empty
(
'node'
,
NULL
);
}
$node
=
node_load
(
$nid
);
if
(
!
$node
)
{
return
ctools_context_create_empty
(
'node'
,
NULL
);
}
// Return the found node in a CTools context.
return
ctools_context_create
(
'node'
,
$node
);
}
As usual, clear the cache: “The Drupal Cache”.
Providing Default CTools Exportables
CTools defines a concept of exportables, in which a set of configuration, such as a view from the Views module or a panel from the Panels module, can be exported into code. The user can modify the configuration in the administrative user interface, and this overrides the configuration in code.
If you want to create a panel, view, or other CTools exportable and save it to code in order to preserve it or share it, you can do so either using the contributed Features module, or directly. “Providing Default Views” shows how to do a direct export of a view; this section describes how to do it for other exportables.
As an example, assume that you have a mini panel that you’ve created in the Panels UI and want to export into code. Here are the steps you would follow to get it exported:
-
Each exportable has a hook that allows you to provide it in code, and you’ll need to locate the name of this hook.
-
The hook name is defined in some code that is in the defining module’s
hook_schema()
implementation in the module’s modulename.install file. In this example, mini panels come from the Mini Panels sub-module of Panels, so the function we’re looking for ispanels_mini_schema()
, and this is located in the panels/panels_mini/panels_mini.install file. -
In the
hook_install()
implementation, locate the data table related to the item you’re trying to export, and within that, find the'export'
array. In this example:
$schema
[
'panels_mini'
]
=
array
(
'export'
=>
array
(
'identifier'
=>
'mini'
,
'load callback'
=>
'panels_mini_load'
,
'load all callback'
=>
'panels_mini_load_all'
,
'save callback'
=>
'panels_mini_save'
,
'delete callback'
=>
'panels_mini_delete'
,
'export callback'
=>
'panels_mini_export'
,
'api'
=>
array
(
'owner'
=>
'panels_mini'
,
'api'
=>
'panels_default'
,
'minimum_version'
=>
1
,
'current_version'
=>
1
,
),
-
If the
'export'
array contains an'api'
section, that means that in order for CTools to recognize your module as a valid provider of these items, you will need to implement (usually)hook_ctools_plugin_api()
. Some exportables may use a different API hook; if so, hopefully they have documented this fact. Here’s the implementation:
function
mymodule_ctools_plugin_api
()
{
return
array
(
// The API version.
'api'
=>
1
,
);
}
-
The
'export'
array may contain a'default hook'
element, which gives the name of the exportables hook we’re looking for. If this is not provided, as in this case, the default name is'default_'
followed by the table name ('panels_mini'
here). This value does not include the'hook'
prefix. So, in our example, the hook ishook_default_panels_mini()
. -
To provide one or more exported items, implement this hook in your mymodule.module file. The return value is an array of exported items, keyed by the machine names of the items. Hopefully, the exportable will have a page that allows you to export the item into code, probably into a large text field on the page. Copy the exported code, and paste the value into your hook implementation function. For example:
function
mymodule_default_panels_mini
()
{
$minis
=
array
();
// Paste exported code here. It starts out:
$mini
=
new
stdClass
();
// ...
// Find this line with the machine name in it:
$mini
->
name
=
'mymodule_test'
;
// ...
// After the export is pasted, you'll have $mini holding one exported
// mini panel. Put it into the return array.
$minis
[
'mymodule_test'
]
=
$mini
;
// Add additional mini panels here.
// Return them all.
return
$minis
;
}
Further reading and reference:
Get Programmer's Guide to Drupal, 2nd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.