Recent Blog Posts

Installing Drupal 8 with Composer and Docker

By Ronald van Belzen | February 29, 2020

The preferred way to install Drupal 8 is with the aid of the PHP package manager Composer (Installing Drupal 8). When you also want to use Docker to create a local development environment you have several ways to approach this challenge. I will give an example of one such approach to set up an environment for Drupal development with Nginx, MariaDB, MailHog and XDebug.

Prerequisite

When you are using a Mac or Windows 10 (Professional or Enterprise Edition) you just need to install Docker Desktop on you machine. For Linux visit the Docker site to determine the tooling you need to install.

When you have Docker Desktop installed and running you need set the disks you want to share in the settings Resources / File Sharing.

Installing Drupal

I did not install Composer on my machine, because I am going to use the Composer docker image from Docker Hub. For that I open my command-line interface and navigate to the directory in which I want to create the subdirectory for my Drupal project and execute the following command:
 

docker container run --rm --interactive --tty --volume ${PWD}:/app composer create-project --no-install drupal/recommended-project myproject

This command will pull the Composer docker image and create and run the container for this image and execute the composer create-project command without installing Drupal. But it will create the subdirectory "myproject" and the "composer.json" and "composer.lock" files. The "--rm" parameter will stop and remove the container after the command finishes.

First we need to change the "composer.json" in an editor by replacing the config parameter by the following config:

    "config": {
        "sort-packages": true,
        "platform": {
            "php": "7.2.28",
            "ext-gd": "1"
        }
    },

You may need to change the php version in your case (or leave it out) and you may need to include php extensions later on, but the "ext-gd" extension is required to be able to install the minimal version of Drupal 8. The Composer installation will fail without it.

Best also delete the "composer.lock" before installing Drupal with composer.

REST API for decoupled Drupal 8 User registration confirmation

By Ronald van Belzen | December 27, 2018

For decoupled Drupal there already exist REST API's for user login, user logout and user registration (the latter since Drupal 8.3). However, Drupal does not yet deliver REST API's for password reset and user registration confirmation. The password reset API is easy to create, but I will give an example for the API anyway as a starter.

The POST for the password reset expects a JSON in the body that looks like this:

{
    "mail": { "value": "foo@bar.com" }
}

As you can see the mail address is used for the identification of the user to which an e-mail will be send. When you need the ability to (also) use the user login name, you will need to adapt the example below.

The API does not actually reset the password. It sends the user an e-mail that can be used to visit a form at an url where the user can change her or his password (without being logged in).

REST API for file upload in Drupal 8 (multipart/form-data)

By Ronald van Belzen | November 30, 2018

This article is related to a former article in which I claimed that "there is no support to directly upload images using REST in Drupal 8". Since Drupal 8.6 this in no longer true (https://www.drupal.org/node/1927648). You can read how to use this new functionality at https://wimleers.com/blog/api-first-drupal-file-uploads and https://wimleers.com/blog/api-first-drupal-8.6.

This article is, however, about an entirely different method to upload a file in Drupal 8 for the somewhat unusual use case of passing the uploaded file to a third party REST api. Not that the method described here will be limited to that use case, but in most cases the new Drupal 8.6 functionality will be the preferred choice.

The situation was that for a decoupled set-up the front-end needed to upload a file to a third party REST api. For that upload the third party needed info about the user present in Drupal that the front-end was not allowed to retrieve or expose in any way. Also, as a temporary solution Drupal needed to perform a virusscan on the uploaded files. The front-end strongly preferred the use of a multipart/form-data content type for the POST request.

So, the uploaded file is not meant for storing in the Drupal application and not meant to be saved as File object. The method shown below does do that anyway, because the clamav module, used for the virus scan, requires it.

As usual I start with a new module:

# my_api.info.yml

name: My REST Service
type: module
description: "REST Service for passing file upload"
package: Web services
core: '8.x'

And since I need to create the end-point for this REST api myself, I create the route for this end-point:

How to set a private or protected property without a setter

By Ronald van Belzen | July 7, 2018

When you have a php class with private or protected properties without setters that you want to set for testing purposes, there are two possible ways to tackle that problem.

As an example I create the following class:

class Example {

  protected $var;

  public function __construct($var) {
    $this->var  = $var;
  }

}

The first approach uses reflection:

$ex = new Example('relection');

$reflectionClass = new \ReflectionClass('\Example');

$reflectionProperty = $reflectionClass->getProperty('var');
$reflectionProperty->setAccessible(true);

$reflectionProperty->setValue($ex, 'another value');

echo $reflectionProperty->getValue($ex);
// Outputs: another value

The second approach uses closure (PHP 5 >= 5.3.0, PHP 7) :

$ex = new Example('closure');
 
$changePropertyClosure = function () {
    $this->var = 'another value';
};
 
$setProperty = $changePropertyClosure->bindTo($ex, get_class($ex));
$setProperty();

$getPropertyClosure = function () {
    return $this->var;
};

$getProperty = $getPropertyClosure->bindTo($ex, get_class($ex));
echo $getProperty();
// Outputs: another value

 

How to override a local task in Drupal 8

By Ronald van Belzen | June 16, 2018

In the previous blog post I added a local task next to the "Published comments" and "Unapproved comments" under the Content "Comments" menu.

The new local task was for the display of a list of comments that were designated as spam comments, and a count of the number of these comments was uncluded. However, this count has in common with the count of unapproved comments that spam comment are unpublished. So the count of unpublished comments will include the number of spam comments.

To correct this we will need to override the local task menu for unapproved comments.

The mechanism for altering the local tasks of another module is by using hook_menu_local_tasks_alter(). The example shown in the documentation adds a top-level menu to all pages, but here we are going to override an existing sub-level menu on only 3 pages.

/* mycomment.module */

/**
 * Set the unapproved comments count.
 *
 * Implements hook_menu_local_tasks_alter().
 */
function mycomment_menu_local_tasks_alter(&$data, $route_name, &$cacheability) {
  if ($route_name == 'comment.admin' ||
    $route_name == 'comment.admin_approval' ||
    $route_name == 'mycomment.admin_comment_spam'
   ) {
    $data['tabs'][1]['comment.admin_approval']['#link']['title'] =
      t('Unapproved comments (@count)', [
        '@count' => get_actual_unapproved_comments(),
    ]);
  }
}

The routes are those of the "Published comments", "Unapproved comments" and "Spam comments". Each level of menus in the $data array is enclosed in each own array item. Index 0 is for the top level menus, while the "Unapproved comments" local task can be found in the $data item with index 1.

The title of the link of the "Unapproved comment" is overridden. The new count of unapproved comments without spam comments is delivered by the function get_actual_unapproved_comments().