Drupal Planet Posts

Write your own Views Bulk Operations actions in Drupal 7

Views Bulk Operations (VBO) is a powerful module that leverages Views to allow site administrators to perform bulk operations on multiple entities at once. It does so efficiently by processing the items in batches over multiple requests to avoid timeouts.

Installing the module will already provide you with a host of various actions you can perform in bulk on various entities. You can publish 1000 nodes at once, delete them or even change their author. And these are just a few examples of what you can do.

In this article we are going to look at programatically creating our own action that we can trigger with VBO to affect multiple node entities. Sometimes you need to write your own because the use case does not quite fit in the extensive list of actions the module provides.

For our example, let's assume we have an entity reference field called field_users on both the article and basic page content types. This field can reference whatever user it wants. And the requirement is to be able to bulk update the value of this field on a bunch of nodes of both these node types at once.

Out of the box, VBO provides us with an action to change the value of a field but this doesn't help us in this case. When adding a value to this field via VBO, we are presented with as many instances of the field as different node types are in the selection. And this is not ideal if we want to scale the functionality to more than one content type. What we want is to select a number of nodes and then only once provide a value to this field. So let's see how we can define a custom VBO action for this use case.

The action

To define a new action we need to implement hook_action_info():

/**
 * Implements hook_action_info().
 */
function my_module_action_info() {
  return array(
    'my_module_my_custom_action' => array(
      'type' => 'entity',
      'label' => t('Add a user to Users field'),
      'behavior' => array('changes_property'),
      'configurable' => TRUE,
      'vbo_configurable' => FALSE,
      'triggers' => array('any'),
    ),
  );
}

With this hook implementation we are defining our own action called my_module_my_custom_action which is available to be triggered on all entity types (because we specified entity for the type) and it acts as a property changer. It is configurable using the default Action API but we don't need any kind of VBO specific configuration. For more information on all the values that you can pass here, feel free to consult the documentation page for VBO.

Next, it's time to create the configuration form for this action, namely the form that will be presented to us to select the user we want to add to the field_users reference field:

function my_module_my_custom_action_form() {
  $form = array();
  $form['user'] = array(
    '#type' => 'textfield',
    '#title' => t('User'),
    '#maxlength' => 60,
    '#autocomplete_path' => 'user/autocomplete',
    '#weight' => -1,
  );

  return $form;
}

The function name takes from the machine name of the action suffixed by _form and is responsible for creating and returning a form array. All we need is one field which uses the core user/autocomplete path to load users via Ajax. Simple enough.

So now after we make a bulk selection and choose our action, we'll be prompted with this form to choose the user we want to add to the reference field. It follows to couple it with a submit handler that will save the value into the context of the operation:

function my_module_my_custom_action_submit($form, &$form_state) {
  $uid = db_query('SELECT uid from {users} WHERE name = :name', array(':name' => $form_state['values']['user']))->fetchField();
  return array(
    'uid' => $uid,
  );
}

The naming of this function is similar to the previous one except for the suffix being _submit this time around. In it, we load from the database the uid of the user that was referenced in the form field by name and return that inside an array. The latter will then be merged into the $context variable available in the next step.

So it's now time to write the final function which represents this step by adding the selected user to the existing ones in that field across all the selected nodes, regardless of their type:

function my_module_my_custom_action(&$entity, $context) {
  if (!isset($entity->field_users)) {
    return;
  }

  if (!isset($context['uid'])) {
    return;
  }

  if (!empty($entity->field_users)) {
    foreach ($entity->field_users[LANGUAGE_NONE] as $ref) {
      if ($ref['target_id'] === $context['uid']) {
        return;
      }
    }
  }

  $user =  array(
    'target_id' => $context['uid'],
  );

  if (!empty($entity->field_users)) {
    $entity->field_users[LANGUAGE_NONE][] = $user;
    return;
  }

  $entity->field_users[LANGUAGE_NONE] = array($user);
}

The name of this function is exactly the same as the machine name of the action, the reason for which we prefixed the latter with the module name. As arguments, this function gets the entity object that is being changed (by reference) and the context of the operation.

We start by returning early if the current entity doesn't have our field_users field or if by any chance the uid key is not available inside $context. Then we loop through all the values of the field and return if the selected uid already exists (we don't want to add it twice). And last, we add the selected uid to the list of existing users in the field by taking into account the possibilities that the field can be empty or it can already contain values. After passing through this action, VBO will automatically save the node with the changes for us.

And that is pretty much it. Clearing the cache will make the new action available in the VBO configuration of your view. Adding it will then allow you to select as many nodes as you want, specify a user via the autocomplete field and have that user be added to the field_users field of all those nodes. And the cool thing is that you can select any node you want: if the field doesn't exist on that content type, it will just be skipped gracefully because we are checking for this inside the action logic.

Hope this helps.

Go custom or use a contributed module?

That is the main question. If you came here looking for a definitive answer, I'm afraid you won't find one. What you maybe will find is a discussion I propose on this topic and my two cents on the matter.

Why am I talking about this?

I've been working on a big Drupal website recently which has many contributed and custom modules. One of my recent tasks has been enabling validation of an existent text field used for inputting phone numbers. The client needed this number in a specific format. No problem, a quick regex inside hook_node_validate() should do the trick nicely. But then it got me thinking? Isn't there a module I can use for this instead? Well yes there is: Field validation.

I installed this module which comes with a plethora of validation rules and possibilities. The default phone validation for my country was not matching the client expectation so I had to use a custom expression for that. No problem, achieved basically the same result. But was this the better option under the circumstances? You might say yes because people have been working on this module a long time to perfect it, keep it secure and provide all sorts of goodies under the hood. Not to mention that it's used on over 10.000 websites.

However, the Field Validation module is big. It has a bunch of functionality that allows you to perform all sorts of validation on fields. This is not a bad thing, don't get me wrong. But does my little validation need warrant the installation and loading into memory of so much code? My custom solution was very targeted and took no more than 10 or so lines of code.

One argument would be that yes, because I may need other kinds of validation rules in the future so you can use this module also for those. But I think that being already very far in the lifetime of this website the odds are quite low of that. And even if this is the case, will an extra 2-3 validation needs warrant the use of this module?

On the other hand, you can argue that your custom code is not vetted, is difficult to maintain, can be insecure if you make mistakes and basically represents some non-configurable magic on your site. These can all be true but can also all be false depending on the developer, how they document code and the functionality itself.

I ended up with the custom solution in this case because on this site I really want to introduce new modules only if they bring something major to the table (performance being my concern here). So of course, the choice heavily depends on the actual module you are considering, the website it would go on and the custom code you'd write as an alternative.

Moreover, please do not focus on the actual Field Validation module in this discussion. I am not here to discuss its merits but the whether or not installing any such module that serves a tiny purpose is the right way to go. This is mostly a Drupal 7 problem as in D8 we use object oriented practices by which we can have as much code as we want because we only load the necessary parts when needed.

So what do you think? Do you have a general rule when it comes to this decision or you also take it case by case basis? If the latter, what are your criteria for informing your choice? If the former, why is this? I'd love to hear what you have to say.

Drupal 8: custom data on configuration entities using the ThirdPartySettingsInterface

In this article we are going to look at how to use the ThirdPartySettingsInterface to add some extra data to existing configuration entities. For example, if you ever need to store some config together with a node type or a taxonomy vocabulary, there is a great way to do so using this interface. Today we are going to see an example of this and add an extra field to the menu definition and store the value in this way.

There are a number of steps involved in this process. First, we need to alter the form with which the entity configuration data is added and saved. In the case of the menu entity there are two forms (one for adding and one for editing) so we need to alter them both. We can do something like this:

/**
 * Implements hook_form_alter().
 */
function my_module_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
  if ($form_id === 'menu_add_form' || $form_id === 'menu_edit_form') {
    my_module_alter_menu_forms($form, $form_state, $form_id);
  }
}

Inside this general hook_form_alter() implementation we delegate the logic to a custom function if the form is one of the two we need. Alternatively you can also implement hook_form_FORM_ID_alter() for both those forms and delegate from each. That would limit a bit on the function calls. But let's see our custom function:

/**
 * Handles the form alter for the menu_add_form and menu_edit_form forms.
 */
function my_module_alter_menu_forms(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
  $menu = $form_state->getFormObject()->getEntity();
  $form['my_text_field'] = array(
    '#type' => 'textfield',
    '#title' => t('My text field'),
    '#description' => t('This is some extra data'),
    '#default_value' => $menu->getThirdPartySetting('my_module', 'my_text_field'),
    '#weight' => 1
  );

  if (isset($form['links'])) {
    $form['links']['#weight'] = 2;
  }

  $form['#entity_builders'][] = 'my_module_form_menu_add_form_builder';
}

In here we do a couple of things. First, we retrieve the configuration entity object which the form is currently editing. Then, we define a new textfield and add it to the form. Next, we check if the form has menu links on it (meaning that it's probably the edit form) in which case we make its weight higher than one of our new field (just so that the form looks nicer). And last, we add a new #entity_builder to the form which will be triggered when the form is submitted.

The getThirdPartySetting() method on the entity object is provided by the ThirdPartySettingsInterface which all configuration entities have by default if they extend from the ConfigEntityBase class. With this method we simply retrieve a value that is stored as third party for a given module (my_module in this case). It will return NULL if none is set so we don't even need to provide a default in this case.

Let us now turn to our #entity_builder which gets called when the form is submitted and is responsible for mapping data to the entity:

/**
 * Entity builder for the menu configuration entity.
 */
function my_module_form_menu_add_form_builder($entity_type, \Drupal\system\Entity\Menu $menu, &$form, \Drupal\Core\Form\FormStateInterface $form_state) {
  if ($form_state->getValue('my_text_field')) {
    $menu->setThirdPartySetting('my_module', 'my_text_field', $form_state->getValue('my_text_field'));
    return;
  }

  $type->unsetThirdPartySetting('my_module', 'my_text_field');
}

Inside we check if our textfield was filled in and set it to the third party setting we can access from the config entity object that is passed as an argument. If the form value is empty we reset the third party setting to remove lingering data in case there is something there.

And that's pretty much it for the business logic. We can clear the cache and try this out by creating/editing a menu and storing new data with it. However, our job is not quite finished. We need to add our configuration schema so that it becomes translatable. Inside the /config/schema/my_module.schema.yml file of our module we need to add this:

system.menu.*.third_party.my_module:
  type: mapping
  label: 'My module textfield'
  mapping:
    my_text_field:
      type: text
      label: 'My textfield'

With this schema definition we are basically appending to the schema of the system.menu config entity by specifying some metadata about the third party settings our module provides. For more information on config schemas be sure to check out the docs on Drupal.org.

Now if we reinstall our module and turn on configuration translation, we can translate the values users add to my_text_field. You go to admin/config/regional/config-translation/menu, select a menu and when translating in a different language you see a new Third Party Settings fieldset containing all the translatable values defined in the schema.

Hope this helps.

Creating pseudo-fields in Drupal 8

In this article we are going to look at how we can create and use pseudo-fields in Drupal 8.

What are pseudo-fields?

Pseudo-fields are simple display fields that you can control from the display settings of a particular entity type. An example of such a field is the core Links field on the node or comment entities.

Why are they useful?

Pseudo-fields are great if you have some content or data that you need to render together with that of a particular entity. And since you don't want to be hacky and hardcode all of this inside a template or preprocessor, you can use pseudo-fields to expose some control over this to the UI. This means that you can use the core drag-and-drop functionality to control their visibility and position relative to regular entity fields.

Pseudo-fields are a great way to display data that is tightly coupled to the entities but is not part of them or their fields.

So how do they work?

There are 2 main steps we need to take in order to create a pseudo-field. First, we need to implement hook_entity_extra_field_info() which is similar to Drupal 7.

/**
 * Implements hook_entity_extra_field_info().
 */
function my_module_entity_extra_field_info() {
  $extra = array();

  foreach (NodeType::loadMultiple() as $bundle) {
    $extra['node'][$bundle->Id()]['display']['my_own_pseudo_field'] = array(
      'label' => t('My own field'),
      'description' => t('This is my own pseudo-field'),
      'weight' => 100,
      'visible' => TRUE,
    );
  }

  return $extra;
}

We mustn't forget to use the NodeType class at the top of the file:

use Drupal\node\Entity\NodeType;

With this implementation we are creating one pseudo-field called My own field that will show up on the display settings of all the node bundles. After clearing the cache, you can already see it if you go to the display settings of any node bundle you have.

drupal 8 pseudo fields

The second step is making this field actually render something when the node is being viewed. For this we need to implement hook_entity_view() or any of its variants:

/**
 * Implements hook_ENTITY_TYPE_view().
 */
function my_module_node_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode, $langcode) {
  if ($display->getComponent('my_own_pseudo_field')) {
      $build['my_own_pseudo_field'] = [
        '#type' => 'markup',
        '#markup' => 'This is my custom content', 
    ];
  }
}

And again, let's use the necessary classes at the top:

use \Drupal\Core\Entity\EntityInterface;
use \Drupal\Core\Entity\Display\EntityViewDisplayInterface;

Above we went with the hook_ENTITY_TYPE_view() variant that applies to the node entity. Inside, we are being passed an EntityViewDisplayInterface display object which we can use to check whether or not our own component (the one we defined earlier) exists in this display. If it does, we add our custom data to the $build render array that is passed by reference. This check allows the user interface to determine the visibility of this component on each view mode, as well as the weight (position relative to the other components which can be either entity fields or other such pseudo-fields).

And that's about it. You can save, edit the display settings of your nodes, create view modes and specify exactly for which one and where this custom content should show up.

Hope this helps.

Debugging Drupal redirects

I was working on a big website with many contrib and custom modules. And I had to debug a very annoying redirect that started happening sometime in the recent past, not sure when. Some pages simply just redirected to other URLs.

I figured out that the problem was one of a 301 redirect. My browser told me that at least. But good luck figuring out where in the code I can find the culprit. Xdebug breakpoints everywhere but to no avail. A search for drupal_goto in the custom modules directory didn't help either, and God be with anyone trying to search through a contrib folder of that size.

Then it hit me. Isn't there a hook invoked inside drupal_goto? At this point I was assuming (and hoping really) that the redirect was happening somehow with a drupal_goto. And it turns out there is one: hook_drupal_goto_alter.

Armed with a new dose of hope, I implemented the hook and cleared the cache. Inside, I added the obligatory $test = ''; statement and put a breakpoint on it. Let's see what happens. After loading one of the offending pages, the breakpoint halted the execution and the Xdebug call stack in my PHPStorm immediately pointed out the problem: Global Redirect. There was some URL rewriting happening on the site so GR got a bit confused and was redirecting back to the original path. The details of the issue are however not important.

My point is that using this hook, I could see exactly who and why was calling drupal_goto. I didn't use it for anything else, apart from learning why the redirect is happening which in turn allowed me to write some code that prevented that.

Awesome. I learned about a new hook. And maybe now you as well.

Adding new HTML tags in the <head> in Drupal 8

In a previous article I've shown you how you can add new html elements to the <head> of your Drupal 7 site. Recently, however, I was working on a Drupal 8 project and encountered the need to do this in D8. And it took me a while to figure it out so I thought I'd share the process with you.

As you know, in Drupal 7 we use drupal_add_html_head() from anywhere in the code to add a rendered element into the <head>. This is done by passing a render array and most of the time you'll use the type #tag. In Drupal 8, however, we no longer have this procedural function so it can be a bit tricky to find out how this is done.

Although existing in Drupal 7 as well, the #attached key in render arrays really becomes important in D8. We can no longer add any scripts or stylesheets to any page without such proper attachment to render arrays. In my last article I've shown you how to add core scripts to pages in case they were missing (which can happen for anonymous users). In essence, it is all about libraries now that get attached to render arrays. So that is most of what you'll hear about.

But libraries are not the only thing you can attach to render arrays. You can also add elements to the head of the page in a similar way you'd attach libraries. So if we wanted to add a description meta tag to all of the pages on our site, we could implement hook_page_attachments() like so:

/**
 * Implements hook_page_attachments().
 */
function module_name_page_attachments(array &$page) {
  $description = [
    '#tag' => 'meta',
    '#attributes' => [
      'name' => 'description',
      'content' => 'This is my website.',
    ],
  ];
  $page['#attached']['html_head'][] = [$description, 'description'];
}

In the example above we are just adding a dummy description meta tag to all the pages. You probably won't want to apply that to all the pages though and rather have the content of the description tag read the title of the current node. In this case you can implement hook_entity_view() like so:

/**
 * Implements hook_entity_view().
 */
function demo_entity_view(array &$build, \Drupal\Core\Entity\EntityInterface $entity, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode, $langcode) {
  if ($entity->getEntityTypeId() !== 'node') {
    return;
  }

  $description = [
    '#tag' => 'meta',
    '#attributes' => [
      'name' => 'description',
      'content' => \Drupal\Component\Utility\SafeMarkup::checkPlain($entity->title->value),
    ],
  ];
  $build['#attached']['html_head'][] = [$description, 'description'];
}

Now you targeting the node entities and using their titles as the content for the description meta tag. And that is pretty much it.

Hope this helps.

Drupal 8: core javascript files for anonymous users

Drupal 8 comes with many performance improvements, one of which being that javascript is no longer indiscriminately loaded on every page. This means that for anonymous users, there are many pages where there is no jQuery or even javascript loaded.

Although this is a good thing, sometimes you do need jQuery (for example to use Ajax, in which case you'd also need other scripts). So how do you load these files? One way is to use hook_page_attachments() to #attach your own library to the page. However, this is only recommended if the assets you need to attach do not apply to something specific, but to the entire page. The recommended way is to attach the library to a render array because then it will only get loaded if necessary.

And if we look at the documentation page for assets, we'll see how we can add our own library. We need to create a my_module.libraries.yml or my_theme.libraries.yml file. And inside, we can add the following:

my_scripts:
  version: VERSION
  js:
    js/scripts.js: {}
  dependencies:
    - core/jquery
    - core/drupal.ajax
    - core/drupal
    - core/drupalSettings
    - core/jquery.once

Where my_scripts will be the name of the library we will reference when attaching.

As you can see, we are not including any javascript or css of our own, we are just making use of the dependency scripts provided by core. So then what is left to do is to attach this library to a render array (or to the entire page if that is your use case):

$render_array['#attached']['library'][] = 'my_module/my_scripts';

Hope this helps.

Custom access control for Drupal 7 entities

Have you ever used hook_node_access() to control access to various node operations on your site? Have you found this hook unbelievably awesome? Thought to yourself, boy, I'm unstoppable now? Well, I sure did.

The problem is that Drupal 7 entities do not start and end with nodes and you may want to control access to other entities in the same way. However, there is no hook_taxonomy_term_access() or hook_fieldable_panels_pane_access() that you can use to apply this tactic. So what can you do in these cases?

The first thing is to understand that each entity implementation is different. Some have a centralised access callback for all the operations while others simply define permissions or access callbacks to be used directly inside the hook_menu() definition. And I'm sure there are also other ways of handling this but these I think are the most common.

In this article we are going to look at two entity examples: taxonomy terms (from core) and fieldable panels panes (from contrib). We will trace the access pipeline from request to response and see what we can do to intervene in this process and add our own logic. So let's begin.

Taxonomy terms

The first entity type we are going to look at is the regular taxonomy term introduced by core. It's actually pretty easy to understand how this entity type is built and what we can do in order to hook into the access pipeline.

If we take a look at taxonomy_menu(), we can quickly figure out which paths it creates to add and edit terms and what access callback and arguments are defined:

Adding terms to a vocabulary

$items['admin/structure/taxonomy/%taxonomy_vocabulary_machine_name/add'] = array(
   'title' => 'Add term',
   'page callback' => 'drupal_get_form',
   'page arguments' => array('taxonomy_form_term', array(), 3),
   'access arguments' => array('administer taxonomy'),
   'type' => MENU_LOCAL_ACTION,
   'file' => 'taxonomy.admin.inc',
);

Editing a particular term

$items['taxonomy/term/%taxonomy_term/edit'] = array(
   'title' => 'Edit',
   'page callback' => 'drupal_get_form',
   // Pass a NULL argument to ensure that additional path components are not
   // passed to taxonomy_form_term() as the vocabulary machine name argument.
   'page arguments' => array('taxonomy_form_term', 2, NULL),
   'access callback' => 'taxonomy_term_edit_access',
   'access arguments' => array(2),
   'type' => MENU_LOCAL_TASK,
   'weight' => 10,
   'file' => 'taxonomy.admin.inc',
);

By nature, viewing taxonomy term pages has to do with node access since they show a list of nodes so we will skip this aspect here and stick with just the add/edit operations.

So let's change the access callbacks and potential passed arguments inside a hook_menu_alter() implementation.

/**
 * Implements hook_menu_alter().
 */
function demo_menu_alter(&$items) {
  $items['admin/structure/taxonomy/%taxonomy_vocabulary_machine_name/add']['access callback'] = 'demo_taxonomy_term_access';
  $items['admin/structure/taxonomy/%taxonomy_vocabulary_machine_name/add']['access arguments'] = array(3, 'add');

  $items['taxonomy/term/%taxonomy_term/edit']['access callback'] = 'demo_taxonomy_term_access';
  $items['taxonomy/term/%taxonomy_term/edit']['access arguments'][] = 'edit';
}

So what happens here? We are simply replacing the access callback definition for these two menu items with our own custom function demo_taxonomy_term_access(). Additionally, we standardise a bit the arguments this function gets: $entity (either term or vocabulary object) and $op (add or edit). This covers both cases.

Now let's write our callback function:

/**
 * Access callback for taxonomy term add/edit operations
 *
 * @param null $entity
 * @param null $op
 * @return bool
 */
function demo_taxonomy_term_access($entity = null, $op = null) {
  if ($op === 'add') {
    return user_access('administer taxonomy');
  }

  if ($op === 'edit') {
    return taxonomy_term_edit_access($entity);
  }
}

Inside the function we run a check on $op and perform the access logic we want. In doing so, we make use of the $entity object that can be either the term being edited or the vocabulary to which a new term is added. In this example we are replicating exactly the intended access checks of the Taxonomy module. To add a term, a user needs to have the administer taxonomy permission while to edit one it needs either the same permission or a vocabulary specific permission (as seen inside taxonomy_term_edit_access()). Now it's up to you to include inside this logic whatever else you need. And you end up with something not so dissimilar to hook_node_access() but for taxonomy terms.

Fieldable Panels Panes (FPP)

FPP is a contributed module that creates an entity type that is primarily used inside a Panels context. Regardless of any of this though, it too exposes CRUD operations on the entities of this type. And consequently, there are access implications. So let's see how we can hook into this pipeline by starting where we did with the Taxonomy module: at fieldable_panels_panes_menu().

By looking inside this hook implementation we can find many defined paths which relate to these CRUD operations. And we also see that many of them have fieldable_panels_panes_access() as the access callback with some specific arguments passed to it.

But what does this function actually do? Nothing but taking the parameters and deferring to the controller class responsible for this entity type and its access() method. And by checking fieldable_panels_panes_entity_info(), the principle function responsible for defining the entity type, we find that this is the PanelsPaneController class. In there, we find all the logic for determining access rights for various operations.

Now that we know all this, what can we do to hook into this pipeline? We could do like before and override the hook_menu() implementation. But since there are so many menu items and the FPP class controller is already doing such a nice job, that may be counter productive. So let's instead override the entity definition and replace the class controller with one of ours that extends PanelsPaneController. In there, we then do what we want.

First, we implement hook_entity_info_alter():

/**
 * Implements hook_entity_info_alter().
 */
function demo_entity_info_alter(&$entity_info) {
  $entity_info['fieldable_panels_pane']['controller class'] = 'DemoPanelsPaneController';
}

Right after this, we create a file inside our module called DemoPanelsPaneController.inc which contains the following to start with:

<?php

/**
 * Overrides fieldable panels panes controller functionality
 */
class DemoPanelsPaneController extends PanelsPaneController {

}

Finally, we edit the module's .info file and make sure it loads this file:

files[] = DemoPanelsPaneController.inc

Then we clear the cache. If all went well, nothing really has changed on the site in terms of functionality. However, the DemoPanelsPaneController class is being used for controlling the fieldable_panels_pane entity type. And since this one extends PanelsPaneController, all previous functionality remains. It follows to now override the access() method and include our own logic to it:

public function access($op, $entity = NULL, $account = NULL) {
   // $account not always full (defaults to current user)
   return parent::access($op, $entity, $account);
}

In the example above, nothing is really changed because the logic is deferred back to the parent class. But you could add some logic in addition or instead of that depending on various contextual factors.

However, I strongly recommend/warn you to stick to the minimum amount of deviation from default is needed, and for all rest, defer back to the original logic. This is to prevent the opening of any security holes. For example, if your access() method looks like this:

public function access($op, $entity = NULL, $account = NULL) {
   if ($op === 'update') {
     return false;
   }   
}

You are indeed denying access to the edit form but now anybody can create and delete entities because there is no more check on those operations. So make sure you understand this and do not leave any loopholes. The fix in this case would be:

public function access($op, $entity = NULL, $account = NULL) {
   if ($op === 'update') {
     return false;
   }   
   return parent::access($op, $entity, $account);
}

If the operation is edit, the access is denied for everybody (probably not a good idea but suitable for this demo purpose). However, if the operation is delete, create or view, we defer to the logic of the parent class to handle those cases. In which case the default FPP permissions will be used.

Conclusion

In this article we've seen two ways we can hook into the access checking pipeline of entities in Drupal 7. We've learned that there is more than just one way of going about it depending on how the entity type in question has been defined. The purpose was to illustrate how you can approach the matter and where you need to look in order to find a solution. Hope this helps.

Dynamically sort complex data with PHP's sort functionality

PHP Logo

In a previous article I showed you how to use the usort() and uasort() PHP functions to sort some more complex data using a comparator callback function. How cool was that?

Although powerful, this technique is quite restricting though because all the logic for the sorting happens inside the comparator function. In other words, you cannot pass parameters to this callback except for the actual default values that are being compared. So what's the problem with this?

Say you have an array of objects that have a name property that can be retrieved by a getName() getter method. And let's say they also have an address property retrieved by getAddress(). And you have a listing of this data and you need to allow for sorting by any of these properties both ASC and DESC. And forget for one second about the possibility of ordering them as they come out of your data store.

Implementing this with just the default usort() function means you will need 4 different comparer functions (one for each combination of property and sort direction). And what if you want to add more columns to the listing? Oh no..

As you can imagine, the solution to this problem is having a dynamic sorting function (or method in a class) to which you can pass the items to be sorted, the property by which to sort and the direction of sort. And then not have to worry about creating all these ridiculous comparer functions. So how might this look like?

function sortByObjectProps(&$items, $method, $order) {
  if ( ! is_array($items)) {
    return false;
  }

  return usort($items, function($a, $b) use ($method, $order){
    $cmp = strcmp($a->$method(), $b->$method());
    return $order === 'asc' ? $cmp : -$cmp;
  });
}

So what happens here? First of all, $items is passed by reference so we don't need to return it. The return value will just be a boolean indicating the success or failure of the sort. Additionally, we pass the method name that retrieves the property value ($method) and the direction of the sort ($order).

Then we run usort() on the items but - and here is the kicker - with an anonymous function that can use the passed in method and order values. Then it's just a matter of comparing the return values of the getter methods and negate the integer if the order is DESC. Pretty cool no?

So now you can have as many columns as you need and get them sorted in both directions. And obviously even more complex stuff.

Hope this helps.

6 steps for new Drupal 8 developers

So you are taking the plunge into learning Drupal 8 for the purpose of developing sites, modules, themes, etc. You're a great Drupal 7 developer, familiar with many drupalisms but you don't have tons of experience with modern PHP frameworks, principles and practices. Well, Drupal 8 still includes many of the old drupalisms but still attempts to keep in line with times and modernise itself.

In this article I want to outline 6 steps I believe you should take to get started learning how to develop custom modules and/or themes in Drupal 8. On top of these 6 builds everything else.

The first three are PHP related in a more general fashion, while the last three target aspects of Drupal 8 itself.

1. Learn Object Oriented Programming

One of the biggest difference between Drupal 7 and 8 for developers is the way code is written. It's still PHP but it's now much more object oriented. Global procedural functions are still in place but most of the logic happens in classes.

In case you don't have much experience with Object Oriented Programming in PHP, this is the first thing you need to learn, brush upon or revise (depending on your level). There are many resources available out there, all scattered as hyperlinks in this section. There are also courses you can take, both free and paid.

Without quite a solid OOP foundation, you won't be able to understand much of how Drupal 8 modules are built.

2. Learn how to use Composer

One of the consequences of modernizing PHP has been the introduction of the Composer package manager. Projects are no longer built without it as it does a great job of installing, updating and managing in general the external libraries and dependencies of your project. Not to mention its very helpful autoloader.

Drupal 8 uses Composer to manage external PHP libraries and dependencies (such as Symfony components, Guzzle, etc) and there is talk about the ability to handle also contrib modules. So make sure you know how Composer works and even start using it in non-Drupal projects.

3. Get familiar with Symfony

One of the main points of contention (back then) in the Drupal 8 development cycle was the introduction of Symfony components. Some people didn't really agree with this great shift from the Drupal way, but others embraced it wholeheartedly. I am in the latter group as I love Symfony and used it even before developing anything in Drupal 8.

Drupal 7 developers are often being told that knowing Symfony is not required in order to develop in Drupal 8. While technically true, you still end up learning a lot of it through the Drupal experience. That being said, I strongly recommend learning some Symfony before. It is a great modular framework and knowing its concepts will really help you out in understanding how Drupal 8 is built (for the components it uses I mean). Once you can build a small website in Symfony, you'll enjoy developing in Drupal 8 that much more because concepts will be similar a lot of the time. Not too mention that you can use Symfony to build apps on its own.

4. Routing and controllers

Just like with Drupal 7, when starting to learn Drupal 8 you need to create the obligatory hello world module (creating a page with a parameter in the URL( usually world) that displays the text Hello + parameter). This example introduces you to many important things:

  • Module folder structure
  • Routing (no more hook_menu) through routing.yml files that map to Controller methods
  • Controller classes which have various methods that can be mapped to routes
  • Access arguments for these routes
  • Rendering markup to the page inside the Controller methods
  • etc

So I really recommend giving this a go.

5. Plugins

Another important concept you'll need to get familiar with is Plugins. Admittedly, this is not the easiest to grasp, but it is super important because it's everywhere. Not to worry though, it's not rocket science.

Many old Drupal 7 implementations of various concepts have been transformed to plugins: info hooks, blocks, field formatters, views handlers, etc. Understanding plugins is therefore very important for being able to extend default Drupal functionality.

6. Dependency injection and the service container

The last step I am going to mention here is dependency injection. Drupal 8 uses the Symfony dependency injection container to manage service instantiation and injection into classes that need them. This helps decouple functionality and increases testability.

However, many people are scared of this concept, mainly because they don't grasp it. I wasn't super comfortable either before understanding it. But you should definitely learn what it means, why we use it and how we use it. Because it is a very simple concept that is used all the time in procedural code as well.

You can already find many tutorials out there on Drupal 8 that load services statically through the \Drupal class. It is much faster to write so people (me included) prefer it when writing about D8. I usually also tend to make a note that using dependency injection is preferred in theses cases.

Not understanding what the service container and dependency injection is will easily let you fall into the habit of just statically requesting services and coupling your code like it was procedural. Once you are clear on this point, this will hopefully not happen any more and the sight of a \Drupal::get('some_service') will make you think twice.

Conclusion

There you have it. What I think are the first 6 steps you should take when learning Drupal 8 for the first time. Of course there are many other important things to learn and do but I believe they build on top of this foundation. Of course, this is me writing so others may have different opinions (very much welcomed in the comments). So let's discuss.