The goal of this article is to compare two very popular CMSs (Content Management Systems)—Drupal 7 and WordPress (for the moment, the latest version is 4.6). The CMSs are considered from developer’s point of view, and we plan to review major APIs of the systems, find analogies, and make a conclusion on which system is better for what kind of projects. The article is not intended to provide full coverage of all technical capabilities.
Basics of use
WordPress was started as a blog engine, but at this moment it is used as a fully functional web development system for different needs. Drupal (sometimes classified as CMF—Content Management Framework) was designed to serve different projects from the very beginning. Installation of both systems takes 5-10 minutes and does not require any special HW capabilities. Both systems are free-of-charge and open-source. After installation, you get a ready-to-go website with a default theme that can be customized to your needs.
As for databases, WordPress supports only MySQL, whereas Drupal can be used with MS-SQL, Oracle, SQLite, and PostgreSQL. After a regular installation, WordPress generates 11 database tables, and Drupal generates about 80 (may be scaring for the first time, but Drupal loves to put everything into a database, including logs).
Both systems provide an admin menu. Drupal has a dedicated theme for the menu (that can be installed and chosen for use), WordPress does not, but the admin menu in WordPress can be customized with plugins. In general, WordPressadmin menu seems to be more user-friendly and easy to use. Drupal’s default admin menu looks like a thing for programmers and professional admins; however, it is a matter of which admin theme you install.
WordPress plugins are easy to install right from the system and usually have a clearer description and end-user hints (such as ‘Hi, I am here now, press this to continue’). There is no such function in Drupal, so a user has to find a module on drupal.org and retrieve URL to install it. It is also possible to just copy the module to the modules folder (you can do the same in WordPress). The other popular approach in Drupal is to use console tool called ‘drush.’ There is a similar tool in WordPress called WP-CLI, but it is probably much less popular.
Update process in both systems is automated and easy to apply. Both kernel and extension updates are downloaded and installed automatically by just pressing a button. There is a significant difference in how product versions are managed. E.g., WordPress supports the only product line with full backward compatibility, so you can update WordPress site to the latest version. On the other hand, Drupal 8 and Drupal 7 are two completely different product lines. Thus, you need significant development efforts to move a site from Drupal 7 to Drupal 8.
Since both systems are in active development and get regular updates, it is highly not recommended to ‘crack the kernel,’ i.e. to modify the kernel or system code. However, both CMSs are very flexible in customizing standard functionality, so if you feel like you need to crack the kernel, then most probably not all customization possibilities are yet investigated.
More details about plugins and modules
Drupal module appears as soon as there are two files: module_name.module and module_name.info, where the first one can be almost empty (PHP tags only) and the second one should contain some basic info. When those two files are in modules directory (in a separate folder or not), Drupal can recognize it and show in the modules section in the admin menu. To start working with a new module, you should enable it in the system. Custom modules (created by a site developer for this particular project) have no essential difference compared to contrib modules (that are part of Drupal module database). The latter ones sometimes get modified to better suit project needs.
Drupal module is supposed to contain some logical portion of functionality, and this is a way to split an app into separate parts (‘if you see something can be isolated/reused then make it a module’). A professional Drupal site, especially using major subsystems such as Drupal Commerce with a lot of interworking modules, can contain several hundreds of contrib and custom modules.
Developing WordPress plugins is as easy as developing Drupal modules—only one PHP file is required (Plugin Name comment in the header of the file is the only must-have). As well as modules, a plugin has to be activated in admin menu to start working. Since there is actually no other place to put the code to (theme file functions.php is not intended to contain a lot of business logic), plugins are the primary construction blocks of the application.
WordPress is frequently criticized for a huge number of plugins available for users. Moreover, nobody can guarantee this particular plugin will not make a security hole in your web application. In Drupal, it looks like there is better society control over module quality, though it is unclear if this may help you to prevent problems. In general, WordPress plugin market seems to be more ‘free’ and diverse (and unsafe), and Drupal modules repository seems to be more professional. Though probably this is just an impression.
One of the main features in working with themes, modules, and plugins is the possibility (and necessity) to use hooks. A hook is a place in kernel code or other module/plugin where there is an opportunity to alter default functionality. There is a lot (hundreds and hundreds) of hooks in both systems.
To define a hook Drupal uses an interesting function-naming technique which does not require an explicit function call. For instance, if you have a function my_module_menu located in a module my_module, then it will automatically serve as a hook to define new routes in the application (hook template ‘hook_menu’).
To define a hook in WordPress, you have to implicitly call add_action (or add_filter) passing a handler function (or closure) as an argument. There may be a different feeling about which approach is better. On the one hand, to define a function and then to call add_action may seem to be redundant. On the other hand, WordPress’s approach has some benefits:
- it contains less magic (and this makes the code more readable and easier to understand)
- add_action approach allows adding as many handlers as you want (in Drupal you can have only one handler per module)
- WordPress’s remove_action function is available to remove a handler; in Drupal, you have to use another hook instead—hook_module_implements_alter, which is a more complicated approach.
In Drupal, you have a theme when there is file themename.info in a folder sites/all/themes. Info file is a simple text file providing basic information about the theme: name, author, page regions, JS and CSS resources to be used, etc. After it is created, the theme has to be enabled in the admin menu and made default.
WordPress theme is defined by two files: style.css, which is the main one and provides theme parameters, and index.php, which is the main template. This scheme was inherited from the times when WordPress was a simple blog engine with the only template file and site styles in style.css. Since that time, much more templates appeared, but the main theme files stayed the same. Assigning style.css as the main theme file may seem not so elegant, but it provides backward compatibility.
Both systems support development of child themes which is handy when working with complicated themes and customizing them for your needs. In the case of a child theme, only *.info and style.css files are must-haves.
Themes are accompanied by files template.php (in Drupal) and functions.php (in WordPress), which define theme customization and custom functions related to the whole theme. When creating a WordPress theme, functions.php is usually used to place theme capabilities (see functions add_theme_support and remove_theme_support), register JS scripts and sidebars. In Drupal, this kind of things is usually done by mouse clicks in admin menu whereas template.php contains functions like template_process/template_preprocess overriding template behavior.
Regarding ready-to-use themes and its customization, WordPress seems to be significantly ahead of Drupal. You can find a lot of ready themes (both free and commercial) which will meet any requirements—classic, modern, regular, responsive, and whichever you need. Besides, WordPress provides so-called customizer API. This API is used to define theme options, and a theme user can customize it from admin menu and see changes in a preview immediately. Some WordPress themes are solid commercial products with regular updates and premium capabilities.
Drupal does not focus that much on theme customization. For a regular theme, you can change the color scheme and basic site parameters such as name and logo. A typical way for a site developer would be to take some minimalistic theme (e.g., Stark) and build on it. Another more advanced approach is to use theme builders (like Zen), which uses responsive design, Sass, and Gulp.
HTTP request processing
In traditional web applications, everything starts from a request and a query string. Thus, using the query string, both systems define how to proceed further. Drupal hosts a dedicated router that maps URL routes to route handlers. The router includes both standard system routes (such as ‘node/1234’, ‘user/123’ or ‘taxonomy/term/123’) and custom routes defined by modules through hook_menu. After processing the query string, the system defines the corresponding route in the router and gets additional route data including so-called ‘delivery callback’—a function to render a page. There are two typical delivery callbacks—default drupal_deliver_html_page and ajax_deliver, which is used to render responses to Ajax requests. It is also possible to define your custom delivery callbacks.
In WordPress, the query string is first used to parse query parameters. Then the system creates global object $wp_query of class WP_Query and sets up conditional tags, such as is_page, is_single, is_category, is_archive, etc. The conditional tags describe the request and what specifically is being retrieved—particular post type, posts from the archive, posts by a category or something else. Both the global object $wp_query and the conditional tags then can be used in further custom code and templates. Basing on the conditional tags, the system chooses which file template is used to render the page.
In general, Drupal way seems to be more systemic and solid (the router is a single source of truth), whereas WordPress approach is easier and is still pretty flexible and customizable. Class WP_Query is a very useful and simple way to generate additional post related DB requests and hook pre_get_posts can be used to modify the main default post request.
Templates hierarchy in Drupal looks as follows. File html.tpl.php is the main low-level template containing essential page markup with the doctype tag. It is rarely overridden because there are better ways to include CSS/JS files (say, even Google Analytics code is inserted using a dedicated module). Then page.tpl.php goes which gets inserted into html.tpl.php as a $page variable and defines the major page markup. Location and markup of regions described in the theme’s info file are also defined in the page template. There are no explicit terms ‘header’ and ‘footer’ in Drupal—they are logically ‘spread’ between HTML and page templates.
Regions in a theme are pretty useful and flexible; you can define as many regions as you like and you get a good set of regions in default themes. Template page contains a variable $content which is a point to insert content. To insert content (which in Drupal is provided by ‘nodes’), there is the template node.tpl.php.
For all mentioned templates, there are default versions, so a theme can work without any template files in its folder. In this case, the templates will be picked up from the kernel modules (e.g. module system). To override a template, you just copy-paste a system template into your theme folder and modify it as you need. Besides typical system templates, modules can define their own templates like it is done in the module Views. Each template is accompanied by a set of variables (usually described in a header of a template) which can be altered in functions like process_page/preprocess_page.
Basically, the hierarchy of Drupal templates is about ‘including’ one template into another—say, a field is included in a node, the node is included in a page, and the page—in HTML. Also, overriding of templates happens when you provide a more specific template (e.g., node-15.tpl.php instead of node.tpl.php).
WordPress template hierarchy is different. All basic templates are ‘full-size’ templates and include doctype. Which particular template will be used depends on current URL request and the conditional tags mentioned above. For instance, if a page is requested (i.e., is_page() equals true), then the system uses page.php, and if there is a category in the request (i.e., is_category() returns true) then category.php is used. If a single blog entry is requested (is_single() == true), then single.php template is used, etc. The special place in the hierarchy belongs to template index.php, which is used to handle all requests when there is no more specific request. A long time ago, index.php used to be the only template in WordPress, and it is still the main one.
In WordPress, there are no default templates like in Drupal (so there is no category.php unless you explicitly define it), but index.php usually has some typical structure with so-called WordPress Loop and can be used as a starting point for developing other templates. In WordPress, you usually explicitly create header.php and footer.php files. They contain raw HTML parts (so header.php usually contains opening tags and footer.php contains closing tags). There are WordPress functions get_header() and get_footer() which will search for header.php and footer.php files and include them into a markup. It looks a bit primitive approach, but it is also very useful and simple. Similar to Drupal’s regions, there are sidebars in WordPress and corresponding functions to use it (see more details below).
Besides typical templates, in WordPress you can also create named custom templates which you can apply to particular pages (when a page is being created, there is an option to choose a custom template, if at least one template exists).
To be able to render the main site page, there are several options to choose from: static page, standard blog view or through a custom page template. Details are here.
As you can see, templates in Drupal are more systemic and probably easier to learn, but WordPress provides very flexible and useful approach, too. A developer should still spend some time to study the templating approach in both systems to use it in the best way.
Regions and sidebars
So, as it was already mentioned, Drupal theme’s info file defines regions and page.tpl.php defines region’s markup and location on a page. There is a specific region template—name.tpl.php, which defines markup of a particular region. This provides a clear structure and flexibility for theme developers. When regions are defined, they become available in the admin menu in a section of blocks. Blocks are visual parts on a page that can output any content and can be moved between regions (using mouse drag-and-drop in the admin menu). Regions actually serve as block containers. A block can be typical (e.g., ‘Powered by Drupal’) or may be defined in some custom module using hook_block_info or may be created right in the admin menu as an arbitrary HTML code. Blocks have their own template block—name.tpl.php.
Widgets in WordPress are exactly the same thing as blocks in Drupal. Widgets can also be typical (e.g., ‘site works on WordPress’) or may be defined in a plugin or a theme (using class WP_Widget and function register_widget()) and can also be defined right in the admin menu as a raw HTML code (more specifically, there is a standard widget that allows putting arbitrary HTML code). Sidebars in WordPress act like regions in Drupal. Sidebars should be registered in a theme’s file functions.php using function register_sidebar(). After a sidebar is registered, it becomes visible in the admin menu’s section Widgets and can contain widgets (also drag-and-drop mouse operations). For sidebars, there is a template called sidebar.php for the default sidebar and sidebar-name.php for all additional sidebars. A sidebar can be rendered in a template using function dynamic_sidebar().
In general, blocks in Drupal are a bit easier to use, but not significantly, and sidebars/widgets functionality seems to be very similar to regions/blocks functionality.
To be able to include JS in WordPress, there is the function wp_enqueue_script and action hook wp_enqueue_script. This is the only way. You can put some logic when defining the hook if you’d like to manage the inclusion (thanks to the need to explicitly use add_action()).
In Drupal, there are several ways:
- specify the path to JS in theme’s info file
- include using function drupal_add_js (similar to wp_enqueue_script)
- include by using libraries API
- include through attach parameter in forms
- include through raw HTML right in template html.tpl.php (not recommended).
The first way is more logical and useful; the second allows adding logic when including the file. Libraries API is a useful thing allowing to register and use entire libraries (JS/CSS/PHP) and manage multiple versions. The registered library can be used by different themes.
To include CSS, there are very similar mechanisms (just replace ‘script’ to ‘style’ and ‘js’ to ‘css’ in the text above).
Due to historical reasons, content in WordPress is called ‘posts’ and content in Drupal is called ‘nodes.’ There are no significant differences but few minor things. E.g., there are two standard content types in WordPress called posts and pages. Pages are not a subclass of posts, but it is a special standalone content type with its own properties. For instance, pages cannot have a taxonomy. Pages are useful to hold static content but could also be defined based on custom page templates as it is mentioned above.
Nodes in Drupal represent all types of content, including pages. All content items by default are accessible by links of type ‘node/node_id,’ e.g. ‘node/12345’, so it is unified.
Content types and fields
Content types are subtypes of nodes and posts correspondingly. In WordPress, content type simply defines a post name and actually nothing else. You can create posts with this defined ‘type,’ and you can retrieve posts from the database using its type.
In Drupal, node type (called bundle) is not only a name. For instance, if you define a set of custom fields and attach it to a bundle, then all node items will get those fields by default.
In core WordPress, there are no fields, but each major CMS entity (post, user, entity or comment) can have meta information. It is just a key-value pair that is attached to an entity instance (not to the whole type). However, a de-facto approach is to use plugin Advanced Custom Fields (ACF), which allows creating sets of fields and attach them to entities in a pretty much the same way as fields in Drupal. The plugin also provides useful API for managing fields programmatically. So, from developer’s perspective, fields capabilities in both systems are similar.
As already mentioned, meta information in WordPress can be attached to all major entities—see functions add_post_meta, add_user_meta, add_term_meta, add_comment_meta. Drupal goes further and explicitly defines ‘entity’ as an abstraction for nodes/users/taxonomy/comments providing common Entity API (so now nodes/users/taxonomies/comments are actually managed through Entity API). Moreover, a Drupal developer can define custom entities—this allows operating with things that are not related to content but still should be considered as separate CMS objects, e.g., an order in an electronic store. Fields in Drupal can be attached to any entity which has property ‘fieldable’ set to true.
Taxonomy is hierarchical content categorization. Both systems support capabilities to define a taxonomy of any level of complexity. Both systems provide a detailed taxonomy API to operate with taxonomy terms and vocabularies. They say, when custom post types and hierarchical taxonomy became available in WordPress, it started to be a fully functional CMS.
One of the most useful contrib modules in Drupal is Views. It allows forming pages and blocks to render nodes in almost any format. Everything can be configured in admin menu (though can be done programmatically, too). Basically, when you create a page in Drupal, you decide to use either a static page, or a node itself (through node.tpl.php), or a view (page or block formed with the help of module Views). A view object defines how the content is formed, and corresponding view module templates define how it will look. The templates can also be overridden by copying it from views module to your theme’s folder and modifying as you wish.
WordPress Loop could probably be considered as a mechanism similar to Views in Drupal. The loop uses global variable $wp_query to retrieve the result of the content fetch. General practice is to use the loop and template tags to output particular parts of the content (title, creation date, post content body, etc.). You can imagine Drupal’s view object as a kind of wrapper for a loop which can be configured through a structured array (Drupal loves structured arrays).
It is hard to say which way is easier to use and more flexible. Views can be created just by mouse clicks, and you have preview to see what you get immediately. From the other hand, more specific programmatic view customization (see functions like views_embed_view) is more difficult than WordPress’s loop. However, when you have configured a view object, you can reuse it again.
Speaking about content rendering, it is important to note a significant difference between Drupal and WordPress. All content manipulations in Drupal are done using so-called renderable arrays. Renderable array is an ordinary PHP hash array containing a lot of structured information about content and how it should be rendered. Renderable arrays become available to custom modules and can be easily modified. In WordPress, you have to do find/replace in a ready HTML string which is not so useful. Renderable arrays are a good thing to operate with, but sometimes they become really huge (several thousands of elements), contain recursive parts, and make a developer scared.
Both Drupal and WordPress evolve and add new capabilities for developers. Those capabilities are structured as APIs, so we will cover some of them.
A separate topic to be considered is called Drupal behaviors. This treats JS code as a kind of behavior that should be executed at some point—not only when a page is loaded but also when some part of a page gets re-rendered, e.g., after an Ajax request. A behavior gets context (which is basically a root DOM element that was changed) and can manage itself correspondingly. This mechanism is pretty interesting and helpful but also requires some time to learn.
Drupal provides pretty powerful API to work with forms using renderable arrays. Forms in Drupal are all recommended to be defined through a renderable array and to be rendered by function drupal_render. When defining a form, you can specify many interesting things, e.g., use Ajax API described above or include a form-specific JS/CSS. It is also possible to create multipage forms. Forms API supports forms validation, multiple form submit handlers and allow overriding by hooks.
Unfortunately, there is no such API in WordPress. There are plugins that allow implementing contact forms for a site and insert it into a page. There are also attempts to develop something similar to Drupal Forms API. In general, it is sad that there is no such basic functionality, even just helpers like Form::open().
More about routes
As already mentioned, routes in Drupal are defined by hook_menu. This way you can create any possible routes (regular, admin routes, Ajax, API, etc.)—it is a unified and easy-to-use approach. There is no unified approach in WordPress; however, there are pretty interesting APIs: Rewrite, which allows defining your own URL rewriting rules, and WP REST API. The latter provides fully functional and ready to use REST interface allowing to get data from DB as well as define your own custom routes. There is no such API in Drupal core, but there is a module that can serve the same goals.
Data models and DB
Data model in CMS is built on content types. If you can describe your application’s business logic using content types, then probably CMS is a good choice. Content type is an entity with some attributes (usually including title and body/description). It is desirable that different content types were independent.
The best way to work with WordPress post types is through an object WP_Query and functions get_posts(), query_posts(). If you need more control over DB, then you can use function dbDelta() which can execute any kind of SQL queries and also class wpdb, which is used through a global object $wpdb. Class wpdb simplifies working with DB, but you still have to write raw SQL queries, and there are no dynamic query building capabilities.
In order to work with DB, Drupal provides several APIs:
1. hook_schema and functions like db_create_table/db_add_field to handle database schema
2. function drupal_write_record as a simple way to write data to DB
3. major API—a set of functions db_select/db_insert/db_update/db_delete/db_query allowing to build a query dynamically (Examples are here)
4. class EntityFieldQuery, which provides nice way to work with entities and fields and also create dynamic queries.
There are also attempts to use ORM systems like Doctrine. Both for Drupal and WordPress there are modules/plugins to integrate Doctrine (WordPress, Drupal), but it looks like they are not in active development. There is probably no real need for an ORM due to a different data model.
Having discussed particular development capabilities, we can make a conclusion that Drupal is much more advanced and sophisticated development platform, but it requires much more time to learn (which is not always available). In practical environment, Drupal’s super-power is compensated by its high complexity. If some Drupal site comes for support to people who are not aware of ‘Drupal way’ of development, then after some time it may end up with a lot of unmaintainable code.
WordPress is a much easier platform allowing to get pretty fast result. It does not provide advanced team development capabilities but still is very flexible. WordPress develops rapidly, getting more and more interesting features every year. Therefore, both CMSs can be seriously considered for particular types of web development tasks.
Drupal is a good fit for large content management systems using sophisticated data models. Having pretty steep learning curve, Drupal though provides rather helpful tools to build any kind of content sites. Node types, custom fields and views that can be managed from admin menu allow a site designer to get a lot of functionality without putting a single line of code. Drupal is not probably the best fit if your site’s business logic contains a lot of entities with different kind of relations (which are hard to describe using Drupal’s data structures).
WordPress would be very helpful in rapid site development using existing components. Available out-of-the-box admin panel and a huge number of ready themes and plugins allow creating a site in minutes, so it is a good fit for one-pagers, CV sites, and blogs. Leveraging WordPress for large enterprise portals is also possible but would most probably require extending the standard WordPress data models. In any case, a site developer has to consider if it is a good idea to use WordPress, taking into account how much of real WordPress functionality (s)he plans to use in the project and how much other/custom code is to be developed.