Feeds

Author

Jun 13 2018
Jun 13
The deadline is today. A remote development team have worked for several weeks on your software. You obtain the long-awaited access to the system. You check it and you are not satisfied with the achieved results. All that was needed to avoid this problem is a team with experience in technology and working using SCRUM. What is SCRUM Wikipedia defines SCRUM as an agile framework for managing work. It is an approach used in many companies to develop software. Full definition can be found here https://en.wikipedia.org/wiki/Scrum SCRUM solves most of the problems arising during software development This is my opinion and many people agree with it. I have been developing commercial projects since 2008. I started as a programmer. Currently, I am supervising high-level projects.
Jun 06 2018
Jun 06
Droopler is a state-of-the-art open source tool for building websites, built on the latest version of Drupal 8. The system has been designed in order to enable easy and flexible modification of content that looks great on every device. You can find out more at www.droopler.com. A demo version of the system is available at https://demo.droopler.pl/. Before you can familiarise yourselves with the possibilities and functionalities offered by Droopler, I will guide you through the set-up process. Good luck!
Mar 16 2018
Mar 16
Yoast SEO is a module that has everything you need to make your content visible to search engines. The plug-in that took the WordPress community by storm is slowly getting more and more traction in the Drupal community as well. What exactly is SEO? SEO is an acronym of Search Engine Optimisation, which basically means optimising websites for a search engine, which in reality translates into getting your website as high as possible in search results, and thus getting more and more people to visit the website.
Mar 05 2018
Mar 05
In one of our previous articles, we showed you how to configure CKEditor in Drupal 8. This time, we are going to demonstrate how you can expand the editor’s functionality on your own.   In the case of many websites, the basic functions of CKEditor are more than enough.  However, there are projects where clients demand expanding the functionality of the content editor. With CKEditor, you can do that using plug-ins – all of them are available on the official website.http://ckeditor.com/addons/plugins/all  Adding a new plug-in to the website based on Drupal 8 is very simple compared to the way it was done in the previous version of Drupal. All you need is to create a simple module.  
Feb 07 2018
Feb 07
The projects implemented by Droptica require a large amount of coding in PHP. Taking advantage of various tool and configurations allows us to streamline the process and make it far more convenient and pleasant. In this post, we wanted to share our experiences and describe an ideal software configuration for coding in PHP (mainly in Drupal) from our standpoint at Droptica. The base software that we use includes Linux (most people use Ubuntu) and PHPStorm as IDE. We also use Docker for setting up and running services like Apache, MySQL, Solr, etc. 
Jan 09 2018
Jan 09
Another year is over, and we are glad to acknowledge that we are very proud of what happened during this period! In short, 2017 was the best year in the company’s five-year history. That is why we decided to share a brief summary of the year with you. See what we achieved over the last 12 months and what we have in store for 2018.    Reasons to be proud
Dec 19 2017
Dec 19
When you’re building an advanced website, you can choose from among many technologies. Usually, the first choice is the programming language (PHP, Python, Java, ASP.NET, etc.) and then we decide whether we should code everything from scratch or use a framework to do all the heavy lifting. There is also a third choice, a more comprehensive solution –so-called CMF systems, which stands for content management framework. Drupal 8 is one of these systems. In this article, I will tell you why it’s worth it to choose Drupal as the basis for any large and advanced website.  By large and advanced, I mean one that is changed frequently. Content changes are carried out by at least one editor, supported by at least one developer (along with a QA tester and a sysadmin), who’s responsible for continuous development of the website, adding new functionalities, fixing the emerging bugs and so on.  Some examples of large and advanced websites:
Dec 12 2017
Dec 12
What is the CKEditor? It is one of many visual HTML editors. It allows you to easily enter text using an interface that resembles such editors as OpenOffice.  It makes text formatting easier through a set of buttons that serve, among others, for things such as: changing the font, text size, adding an image, making lists, and many others. The CKEditor is the default text editor in the Drupal 8 system. You do not need to add any libraries or modules in order to use it.  Enabled editor in the contend adding form By default, the editor is enabled for two text input formats:
Nov 23 2017
Nov 23
Everyone who codes in Drupal will sooner or later encounter the need to define tighter control of access to content. The standard mechanisms of roles and permissions are very flexible, but they may be insufficient in complex projects. When access to nodes starts to depend on, for example, fields assigned to a given user, then you have to take advantage of more advanced solutions. In Drupal 7 and 8 we can use a hook – hook_node_access() or a so-called grants mechanism.
Jul 31 2017
Jul 31
Has it ever happened to you that when you were looking on a website, you weren’t sure whether a font you used was 12 pt or 13 pt? Or maybe you kept looking at an image, wondering whether it had been moved slightly to the left before? If the layout is a priority on your website, maybe it’s time to think about automating the testing of this aspect of your project. VisualCeption is a noteworthy solution for exactly this use case. In this article, we are going to show you how to launch this add-on on a project built using the docker-console. Just like in our previous articles, our examples will be based on the project that you already know from our previous posts.
Jul 14 2017
Jul 14
Taking into consideration the fact that most of our products are based on Drupal, the tests should also naturally work best with such projects. This is why we decided to complement the standard functionality of Codeception with some new modules dedicated for Drupal. As in our previous article, all examples listed below will be based on a project based on docker-console, which is why we encourage everybody to read the previous articles first if you didn’t do so yet. If you already have your Codeception project and just want to slightly modify it so that it works better with Drupal, this article is also for you.
Jun 26 2017
Jun 26
If you read our previous posts, you already know very well how to start a project in the docker-console. If you haven’t done it yet, you should start with this article, because for the purpose of this article we assume that your project in the docker-console is already up and running, therefore all commands executed below will refer to it. In this article, we would like to introduce you to the world of automatic tests using Codeception, based on this kind of a project. Of course, not everyone has to automate all the tests on their projects, but if this will not require too much work, we bet that many people will look at a set of “smoke tests” favourably, to say the least. 
Jun 06 2017
Jun 06

People who follow our blog already know that we’re using Docker at Droptica. We've also already shown you how easy it is to start a project using our docker-drupal application (https://www.droptica.pl/blog/poznaj-aplikacje-docker-drupal-w-15-minut-docker-i-przyklad-projektu-na-drupal-8/). Another step on the road to getting efficient and proficient with Docker is docker-console application, which is a newer version of docker-drupal, and exactly like its predecessor it was created in order to make building a working environment for Drupal simple and more efficient. How does it all work? You are going to see that in this write-up. Since we're all working on Linux (mainly on Ubuntu), all commands shown in this post were executed on Ubuntu 16.04.

Installing Docker

Of course, our adventure starts with installing Docker, you could read about this in one of our earlier posts. Just for the sake of clarity, in order to install Docker, you have to execute the following commands:
 

wget -qO- https://get.docker.com/ | sh 

sudo usermod -aG docker $(whoami) 

sudo apt-get -y install python-pip

sudo pip install docker-compose

Installing docker-console

Another step is to install the docker-console application. In order to do so, you should first install python-pip package, which allows to quickly install and upgrade our application:

sudo apt-get update && sudo apt-get -y install python-yaml python-setuptools python-pip python-dev build-essential 

Now all you have to do is to install docker-console, which can be done using the following command:

sudo pip install docker-console 

After the installation completes, a new command – docker-console (or dcon for short) – will be available in your terminal. After launching it, you should get a list of available options and actions:

Docker console available options and actions

Initialisation of a new project

Now we can finally begin our adventure with docker-console. To start with, let's assume that we don't have any Drupal project available, so we have to start a new one. This is possible by using the following command:

docker-console init --tpl drupal7 

Project's structure with visible folders

After running it, it should generate an entire project structure for us (like on the image above), which will allow us to quickly and efficiently organise a project. The command is used for generating Drupal 7 project structure, but nothing stops you from using a command to generate a Drupal 8 project instead:

docker-console init --tpl drupal8 

However, for the purpose of this guide, I will stick to a Drupal 7 project. The next step is downloading an appropriate version of Drupal from https://www.drupal.org/project/drupal and unpacking the archive to the “app” folder.

Start the containers

At that point, in order to start working with the project, you will also have to launch the containers with services required for the page to work (e.g. Apache, MySQL, PHPMyAdmin, Nginx – in a traditional working environment, you would have to install them locally on your machine). All the services we will need for work should be added and configured in docker-compose.XML file (the file for this project is presented below and can be found in the main folder of the project.) We build our docker-compose files based on version 1. If you would like to learn more about how it’s built, you should visit the project’s website:  https://docs.docker.com/compose/compose-file/compose-file-v1/ 
The configuration you can see below will launch three containers:

  • web: Apache and PHP,
  • MySQL,
  • PHPMyAdmin.

Three containers ready to be opened

In order to start the containers, execute the following command:

dcon up 

After a moment (a longer moment during the first launch, because all the images will have to be downloaded first) you should see a result that’s similar to this:

Containers opened up and running

The website is now built, you can go to the container’s IP address and take a look. You can check the IP address by running “docker inspect”.

docker inspect --format "{{ .NetworkSettings.IPAddress }}" dockerconsoleprojectexample_web_1 

In my case, the address was  http://172.17.0.23/ (it may be different, depending on the containers you launch).

Installing Drupal

After going to that address, you should see Drupal installation screen. It is by no means different than a standard one, therefore I am not going to write a step-by-step instruction. However, it is very important to use the data from your docker-compose.yml file when prompted to configure your database, exactly as it is shown in the illustrations below. The database name should be in line with MYSQL_DATABASE parameter, same goes to the user details (the username can be set in MYSQL_USER with a password stated in MYSQL_PASSWORD, you may also decide to use root, the password for root is set in MYSQL_ROOT_PASSWORD), you also have to enter the name of our database container as a host, otherwise you will not be able to connect to it.

All names are in line, database connected

All names are in line, database connected

Website launched

Now, when your website is ready and available at the container’s IP address, you can implement your changes and add content. You should, however, keep in mind that everything is happening in Docker containers, so if you remove them, everything – including the database – will be lost. This does not apply to the application files since they are only mapped from a local folder to the app folder in a container. If you want to secure your database and prevent losing it even if you remove the containers, you should also map your database files to a local folder. By doing so, you will allow them to synchronise with a folder in the container. What is more important, the database is not deleted when the containers are just stopped, for example when you turn off your computer. In this case, you can quickly relaunch them by running “dcon up” again. In another example, we will dump the database, which we will be able to use later in order to build another project.

Rebuilding a project

The first thing you should do is dump your database and files. To do this, I used the backup and migrate module ( https://www.drupal.org/project/backup_migrate), but you can do it in any way you like. It is, however, crucial that the files and the database end up as tar.gz archives named database.sql.tar.gz and files.tar.gz. After that, place the database file in the app_data folder, then move the compressed files to app_files.
If you would like to get to the PHPMyAdmin container in order to dump the database, you can, of course, do it just as easily by going to the IP address of the PHPMyAdmin container in your browser and logging in with the data from docker-compose.yml file shown above.
If you did not go through all the earlier steps, you can download my project, database and files. This way, you will be able to start your adventure with docker-console without creating your own project after installing docker, docker-compose and docker-console.
Project repository: https://github.com/DropticaExamples/docker-console-project-example
Database dump: https://www.dropbox.com/s/tcfkgpg2ume17r3/database.sql.tar.gz?dl=0
Project files: https://www.dropbox.com/s/hl506wciwj60fds/files.tar.gz?dl=0
In order to clone my project and launch the service containers, you will have to run the following commands in your terminal:

cd

git clone https://github.com/DropticaExamples/docker-console-project-example

cd docker-console-project-example 

git checkout docker-console-start 

cd app_databases wget https://www.dropbox.com/s/tcfkgpg2ume17r3/database.sql.tar.gz?dl=0 

cd ../app_files wget https://www.dropbox.com/s/tnl0ftfluyr5j7p/files.tar.gz?dl=0 

cd .. 

dcon up 

Otherwise, if you are using a project created beforehand, all you have to do is just:

dcon up 

Unless, of course, you had the containers already launched, in this case, you will not have to start them again (running “dcon up” when your containers are up and running should only tell you that they are running and up-to-date.)

Installed and running containers

If you are not sure whether your docker containers are built and active, you can check it using:

docker ps -a 

If everything went well, you should have three containers up and running:

  • web: Apache and PHP,
  • MySQL,
  • PHPMyAdmin.

Building a project

In this step, we are going to restore our working environment based on the files and the database, which were placed in proper folders beforehand. By doing so, we can constantly work in an environment that is close to production. Now we can build a website on working containers. In order to do so, execute the command:

dcon build 

In previous examples, we did not do it, because there was no need to start a project building sequence – there are no files and the database is empty when you are starting a new project). In this example, the building process covers the following steps:

  • confirmation that the building process should start;
  • copying settings;
  • cleaning database;
  • importing database;
  • unpacking files;
  • cleaning cache (drush cc all);
  • generating login link (drush uli);
  • changing file permissions.

This is why when we want to import a database and files to a project, we have to run dcon build.

Website’s ready

The website is now built and you can take a look at it exactly like before, by going to the container’s IP address.

Website is ready!

Summary

This is how you can quickly set up a project based on Docker and docker-console. At your first attempt, it may seem that the procedure is quite complicated, but you can take my word that you will quickly see a return on all your time invested into learning Docker, especially when you will want to quickly add a new service to an existing project or test some other configurations. Of course, this post is not fully exhaustive, and if the time allows, we are going to write more about docker-console. In the meantime, I encourage everyone to read the project’s readme file at  https://github.com/droptica/docker-console and try to set up your own project with Drupal 8. Let us know how it went :) We are also going to share the DrupalCamp Wrocław 2017 videos with you soon, featuring Maciej Łukiański talking about docker-console and some other stuff. 

Apr 04 2017
Apr 04

In Drupal 8 we can easily add our own command to Drush. The whole code for the new command will be put in a custom module.

We have to create only 2 files.
The structure looks as follows.

The structure of the file visible as tree of catalogs

File d_drush_command_pack.info.yml

A standard file of every module in which there is information about our plugin.

name: Droptica Drush Command Pack
description: Provide useful custom drush commands
package: Drush Commands
type: module
core: 8.x

File d_drush_command_pack.drush.inc

Here, we define our commands and we put our logic that will be executed by a given command.
The following example generates two new commands.

<?php

/**
 * Implements hook_drush_command().
 */
function d_drush_command_pack_drush_command() {   $commands['rebuild-aggregate-file'] = [
    'description' => 'Rebuild aggregate css/js files',
    'aliases' => ['raf'],
    'arguments' => [      
       'js' => 'Rebuild aggregate js files only',
      'css' => 'Rebuild aggregate css files only',
    ],
    'examples' => [       
      'drush raf' => 'Rebuild aggregate css/js files',
      'drush raf js' => 'Rebuild aggregate js files',
      'drush raf css' => 'Rebuild aggregate css files',
    ],
  ];

  $commands['delete-node-alias'] = [
    'description' => 'Delete a node alias',
    'aliases' => ['dna'],
    'arguments' => [       
      'source' => 'Delete by source',
      'alias' => 'Delete by alias',
    ],
    'options' => [      
        'name' => 'Name of source or alias',
    ],
    'examples' => [       
      'drush dna source /node/1' => 'Delete an alias by source path',
      'drush dna alias /path-alias-name' => 'Delete an alias by alias path name',
    ],
  ];

  return $commands;
} /**
 * Drush command logic for rebuilding file cache.
 *
 */
function drush_d_drush_command_pack_rebuild_aggregate_file($arg = NULL) {   $tokens = ['@arg' => $arg];
  switch ($arg) {     case 'js':
      \Drupal::service("asset.js.collection_optimizer")->deleteAll();
      _drupal_flush_css_js();
      drush_print(dt('Rebuild aggregate @arg files.', $tokens));
      break;
    case 'css':
      \Drupal::service("asset.css.collection_optimizer")->deleteAll();
      _drupal_flush_css_js();
      drush_print(dt('Rebuild aggregate @arg files.', $tokens));
      break;
    case '':
      \Drupal::service("asset.css.collection_optimizer")->deleteAll();
      \Drupal::service("asset.js.collection_optimizer")->deleteAll();
      _drupal_flush_css_js();
      drush_print(dt('Rebuild aggregate js/css files.'));
      break;
    default:
      drush_print(dt('Wrong argument. Possible arguments: js, css or use command without argument.'));
  } }

/**
 * Drush command logic for deleting aliases
 *
 */
function drush_d_drush_command_pack_delete_node_alias($arg = NULL, $opt = NULL) {   $tokens = ['@arg' => $arg, '@opt' => $opt];

  switch ($arg) {     case 'alias':
    case 'source':
      $condition = [$arg => $opt];
      \Drupal::service('path.alias_storage')
        ->delete($condition);
      drush_print(dt('Alias @opt deleted', $tokens));
      break;

    default:
      drush_print(dt('Wrong argument and options. Example use: drush dna source /node/1'));
  } }

The above code creates two new drush commands for us
The first one is rebuild-aggregate-file
The second one is delete-node-alias
The commands are defined in the hook:   hook_drush_command(),
here, we set alias, arguments, options and examples for our command
Logic for each of them is created in separate functions.
We create the names of the functions like this: 
drush_[MODULE_NAME]_[COMMAND_NAME]()
All we have to do is turn our new module on, clear cache and test the commands.
Examples of launching the commands from the above code.

drush rebuild-aggregate-file css
drush raf js
drush raf

drush dna source /node/1
drush dna alias /alias-name

You will find the files of the sample module on GitHub.

Mar 01 2017
Mar 01

Content creation using the Paragraphs module is a completely different approach compared to the "standard" content creation in Drupal.
In short: we prepare the components (paragraph type) and then, during the creation of an entry, we select any of the available components. 
The components can be simple elements containing text, contents with columns or such complex elements as sliders, videos or photo galleries. There are practically no constraints; we can define the available components by ourselves, which will then be used by people dealing with content management. 
We can prepare our own templates for the components (tpl.php files). Properly prepared templates and styles for them will ensure the same display of components, regardless of their place of use and the device (responsiveness).
It is a much more flexible and repeatable solution than creating such things in WYSIWYG editors, where the elements do not always look like we want them to be, not to mention adapting such content to a mobile versions of a website.
The result is a nicely formatted page of elements that are repeatable and consistent throughout the whole website.

Paragraphs is a great convenience for the editor of a website

Nothing can be more upsetting for a website editor than when during adding the content, he fills it with photographs or other elements and after saving, the entire content does not look like it was during the editing process.
Attempts to fix this are usually difficult, time-consuming and inconvenient (you must often add styles in the WYSIWYG view source mode). For people unfamiliar with HTML and CSS it will often become an insurmountable problem. 
The properly prepared configuration of the Paragraph module saves time for those involved in content management. They can concentrate on the content, that is – on what they actually should be doing, and not on text formatting (which should be automatically done by CMS).

Content creation with the paragraph module

On the page with the content adding form, we will see a list with types of components (defined by the programmers) available in the system.
In one entry, we can choose an infinite number of components (of the same type or different types) and then freely change their order.

paragraph with addition

Below are shown exemplary types of paragraphs.

Slider Slick with pictures added during content editing

slick slider

And here is how creating a slider looks like – it is a simple thing to do as you just add pictures.

adding new photos

A simple text field

simple text area

It can be freely styled and each time it will look the same.
In addition, to each paragraph, we can hook up its own predefined class with the styles.
For example, changing the background colour or background image is only a matter of choosing the appropriate class.

adding the predefined class

 

Multi-column fields
Text with a picture

text with photo

Three pictures next to each other

three photos together

The module forms the programmer's side

Using the module and creating new types of paragraphs is very easy for a programmer.
Everything is done on the page and in most cases, the whole process consists of adding the appropriate boxes for a given type.
In the examples in the text, aside from the Paragraphs module, we also use the Classy paragraphs module. Thanks to that, we can attach predefined CSS classes to the selected paragraph.

Creation of a new type of the paragraph

Structure -> Paragraphs types
admin/structure/paragraphs_type

creating the new paragraph

After adding the paragraph, you just have to add the boxes. 
Many types of boxes are available and located in the Drupal's core as well as those from the contrib and custom modules. 

managing the fields

Once we have created the types, they must be "hooked" to the entity eg. the content, block or any other serving the boxes.
Linking is performed with a special field: Reference revision - Paragraph
In this case, it adds the paragraphs to the Page type of content

adding a field

In field settings we select which types of paragraphs we want to have available in a given type of entity. If we do not select anything, then all types are available.

all types available

 We just have to save; and during the adding of content, any paragraph can be introduced.

new paragraph enabled

Adding your own CSS classes to the paragraph.
The Classy paragraphs module is a useful tool if you want to give editors a possibility to eg. change the background colour of a given paragraph. 
Of course, we do not have to limit ourselves to just changing colours. All that CSS offers can be implemented here.
Add a linking field to the paragraph.

adding a field

 

After saving, choose the Classy paragraph styles as a reference.

classy field

At this stage, it is all.
Now you have to define classes so they can be hooked to the paragraph.
You can define them with Structure ->Classy paragraphs style

classy paragraph style

Here are the defined three simple classes for changing the background colour.
The settings are important when it comes to choosing the name of the class. 
Remember that the label should be understood by the editor, it is precisely this text that will be displayed during the selection.

editing classy paragrapgh styles

All the magic happens already in CSS.

"css "magic""

From this point, the editor can choose the class when creating a paragraph.
As you can see in the example, there can be several such fields with classes.

fields with some classes

References:

https://www.drupal.org/project/paragraphs 
https://www.drupal.org/project/classy_paragraphs 
https://dev.acquia.com/blog/drupal-8-module-of-the-week/drupal-8-module-of-the-week-paragraphs/15/03/2016/9881

Jan 05 2017
Jan 05

Over the course of recent years, websites have undergone a significant change. Many new services and social media websites have been created, mobile applications now play a very significant role on the internet, and various on-line services exchanging data is nothing out of the ordinary anymore. We could even say that integration with external services is standard now, especially in the case of large e-commerce applications or on-line payment systems.
Here we may ask ourselves a question: can we communicate with external services using Drupal? Of course we can. This functionality was available in old versions of Drupal, but with Drupal 8, the RESTful Web Services API module started being included by default in Drupal core, which shows that it is an important and much-needed element in complex on-line applications.

Today we are going to discuss one of the more popular data exchange formats - JSON.

In our example, we are going to use Drupal 8.2^ and jQuery to write a simple JavaScript application. We are going to use Drupal as our CMS for managing data, and our content will be displayed on a “light” HTML website.

1. First, we install Drupal 8 and turn on our modules:

  • The RESTful Web Services API is new in Drupal 8
  • Serialization

chosen services being ticked

2. Then we download and turn on the REST UI module (this step is optional but useful).

https://www.drupal.org/project/restui
This module enables the user to manage access and data format via the UI (administration panel). It works with Drupal 8.2 or newer.

3. Configuration using REST UI - displaying the node in JSON format.

Now we are going to adapt our content type to integration. In our example, we are going to use the NODE entity. Go to /admin/config/services/rest. Select “content type” and set format json, auth cookie in the GET method. In this case, we are only going to download data, so the GET method will be perfectly fine.

Drupal 8 rest UI

If you decide not to install the REST UI module, you will have to enter the settings for all entities manually in config files. The above settings look as follows:
Filename:  rest.resource.entity.node.yml

uuid: add2fdec-b11f-45ad-a74f-3d30e36ad72f
langcode: en
status: true
dependencies:
  module:
    - node
    - serialization
    - user
id: entity.node
plugin_id: 'entity:node'
granularity: method
configuration:
  GET:
    supported_formats:
      - json
    supported_auth:
      - cookie

4. Let’s now create our node.

Assuming that our new node is located at http://www.drupal8.dev/node/1
we have to add a ?_format=json parameter, which gives us the following address: domain.example/node/1?_format=json 

If everything is configured properly, we should now have a node object in JSON format. If you display JSON content in your browser, you can install an add-on or extension for formatting JSON code (the one you can see on the screenshot is JSON Lite https://addons.mozilla.org/en-US/firefox/addon/json-lite/?src=api)

drupal node in JSON format

5. List of nodes in JSON

All right, so downloading single nodes is already working. Let’s now try to get a list of nodes.
We are going to use the Views module.

views - creating json

Provided that we are not going to use the majority of available data, we can select just the fields that concern us in order to reduce the amount of data transferred.

JSON rest options in views

To sum up: we created a list of content located at /node_list, and by adding ?=_format=json to our URL we can download any of them in JSON format.

6. Own address for REST/JSON (endpoint)

If - for any reason - the above solutions aren’t enough, we can create our own endpoint and add new data to our JSON.
Let’s create routing and a controller.

Routing

First, let’s start from creating a routing at, e.g. /v1/custom_endpoint. Entering our API version in the URL is definitely good practice, since while creating a newer version we can leave it at /v2/custom_endpoint, /v3/... etc. This way our users who use an older version of the API won’t get cut off from our resources.
Routing example:

ev_client_endpoint:
  path: '/v1/custom_endpoint'
  methods:  [GET]
  defaults:
    _controller: 'Drupal\ev_client_endpoint\Controller\EvEndpoint::get'
  requirements:
    _permission: 'access content'

Explanation: by requesting GET at /v1/custom_endpoint we are going to receive the data returned by EvEndpoint controller executing GET method.

Controller

For this example, we will assume that our endpoint is supposed to return basic information about our website - the name and e-mail address, current time stamp and a random node with ID of 1 through 10.

<?php

namespace Drupal\ev_client_endpoint\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\node\Entity\Node;
use Symfony\Component\HttpFoundation\JsonResponse;

/**
 * An example controller.
 */
class EvEndpoint extends ControllerBase {

  /**
   * {@inheritdoc}
   */
  public function get() {

    $response = new JsonResponse();
    $config = \Drupal::config('system.site');
    $node = Node::load(random_int(1,10));
    $data = array(
      'date' => time(),
      'site_name' => $config->get('name'),
      'site_email' => $config->get('mail'),
      'random_node' => array(
        'title' => $node->get('title')->getValue()[0]['value'],
        'body' => $node->get('body')->getValue()[0]['value'],
      )
    );

    $response->setData($data);

    return $response;

  }
}

After doing everything correctly, we can clean our cache and go to /v1/custom_endpoint. The end result should be as follows.

​
{
"date": 1481920261,
"site_name": "custom endpoint site",
"site_email": "[email protected]",
"random_node": {
"title": "title node 5",
"body": "body node 5"
}
}

​

7. Receiving JSON

Let’s now create a simple JavaScript to display our data on the website.
If we are going to connect from domains other than our Drupal, the https://www.drupal.org/project/cors, adding headers to requests or creating a virtual proxy might be useful, so that all the requests are seen as local (you can find out more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS)

Code html index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
    <script src="http://www.droptica.com/blog/drupal-8-restjson-integration-simple-javascript-application//app.js"></script>
</head>
<body>

</body>
</html>

Now we can add our app.js file and jQuery library in the Head section.
Script app.js

$(document).ready(function () {
  $.ajax({
    type: 'GET',
    url: 'http://drupal8.dev/v1/custom_endpoint',
    success: function (data) {
      {
        var date = new Date(data.date * 1000);
        $('body').append('' +
          '<h1 class="name">' + data.random_node.title + '</h1>' +
          '<content class="body">' + data.random_node.body + '</content>' +
          '<email class="mail">' + data.site_email + '</email>' +
          '<div class="date">' + date  + '</div>' +
          '<h2 class="email">' + data.site_name + '</h2>'
        );
      }
    }
  });
});

Download JSON from our Drupal: /v1/custom_endpoint

The data variable contains our json, now we can add it with our variables to the HTML body using append function.

If everything has been done properly, upon displaying index.html in our browser, the JavaScript requests the data from our Drupal and downloads it in JSON format, and then adds them to the body of our website.

Summary

These are some basic information regarding starting the adventure with web services using Drupal. In the next part, we are going to write about logging into Drupal using an existing user account, as well as adding and editing content via REST/JSON.
If you want to further expand your knowledge about D8 and other tools useful for designing web applications, give us a like on Facebook, where we share our tutorials, guides and various interesting stuff from the industry. You can also take part in Drupal Day and the Drupal Camp! New content is also coming soon to our blog!

Dec 13 2016
Dec 13

Time has already passed since the premiere of Drupal 8. New projects and modules are released all the time, and there is an ever-increasing number of projects compatible with version 8.x at drupal.org. Sadly, these versions are often still unstable and filled with various bugs. As of today, the number of additional modules available for Drupal 8 is around 2,250, compared to 12,400 Drupal 7 modules. It is immediately visible that the range of ready-made solutions is far more narrow in the case of the new generation, compared to the older one. However, even though in the case of advanced projects, writing them from scratch might be pointless, in the case of smaller add-ons, we can try to port the entire module or its interesting functionalities to version 8.x. In this guide, we are going to port a module used by us to display the cookies disclaimer on our website to be used with Drupal 8.

v. 7.x structure image

This is what the file structure of v. 7.x looks like. The module is fairly simple, as it contains a custom CSS file, a JavaScript, two templates, one included .inc and the standard .info and. module files.
In order for the script to be visible by Drupal 8, we are going to need an .info.yml file. Let us start with this one. In D8, it is the only file required for the module to be seen by the system.

Drupal 7

cookiec.info

name = CookieC
description = This module aims at making the website compliant with the new EU cookie regulation
core = 7.x

Drupal 8

cookiec.info.yml

name: CookieC
type: module
core: 8.x
version: 8.x-1.0
description: This module aims at making the website compliant with the new EU cookie regulation
configure: cookiec.settings

The files are fairly similar – as you can see, the main difference here is the file format. Since Drupal 8 uses YML files, the file needs to have an .info.yml extension. Additionally, we also have to add type: because skins are using similar syntax, in our case this is going to be type: module. Additionally, in this file, we can add ‘configure:’ – a routing hook to the configuration page. However, we are going to discuss routing in the next part. It is worth mentioning that – just like in Python – indentations in the code are crucial. After purging the cache, the module should be already visible on the list.

CookieC visible on the list

Let’s now see what can be found in a D7. module file. Our module uses the following hooks/functions:

  • hook_menu – used for defining /cookiec page, which displays our cookie policy,
  • hook_init – used to initiate our functionality, including the function loading our custom css and js files,
  • hook_permission – permission for administering our module,
  • hook_theme – template definitions.

In D8 hook_menu, hook_permission and hook_init were done away with.
The tasks of hook_menu are now covered by “module_name.routing.yml”, and instead of hook_init we are going to use EventSubscriber.
 

Drupal 7

Hook menu included in the .module file

<?php
/**
 * Implements hook_menu().
 */
function cookiec_menu() {
 $items['cookiec'] = array(
  //'title' => '',
  'description' => 'Cookie policy page.',
  'page callback' => 'cookiec_policy_page',
  'access arguments' => array('access content'),
  'file' => 'cookiec.page.inc',
 );
 return $items;
}

Drupal 8

Contents of the cookiec.routing.yml file:

cookiec.render_cookiec:
  path: '/cookiec'
  defaults:
    _controller: '\Drupal\cookiec\Controller\Cookiec::renderPage'
    _title: ''
  requirements:
    _permission: 'access content'

The *.routing.yml file contains the name of the routing – cookiec.render_cookiec.
Path: URL address, allowing us to access a given functionality; as in D7 we can also use dynamic paths, such as for example: path: 'example/{user}', in this case it won’t be needed, since our module displays only a static page.
defaults: _controller: '\Drupal\cookiec\Controller\Cookiec::renderPage’ at /cookiec content will be displayed, returned by Cookiec class using renderPage() method. We will write about this in detail while creating /cookiec. static page.
Requirements: We are going to add access permissions in _permission: access content.
We should create all the classes and methods assigned to our routings at once in order to avoid displaying errors.
We are going to create a simple “Hello World” example. Let’s start by creating a Cookiec.php file in /src/Controller:

namespace Drupal\cookiec\Controller;

use Drupal\Core\Controller\ControllerBase;

class Cookiec extends ControllerBase {

 function renderPage(){

  return array(
   '#title' => '',
   '#markup' => 'hello word!',
  );
 }
}

After purging the cache and going to /cookies we should get a page saying “Hello World”.
 

In order to create our own permissions, we are going to create module.permission.yml file, similarly to other .yml files:

administer cookiec:
 title: 'Administer cookiec administration message module'
 description: 'Perform administration tasks for cookiec'

We also add the following to our routing:

requirements:
 _permission: 'administer cookiec'

Using the form class is a useful solution in this case.

cookiec.settings:
 path: '/admin/config/content/cookiec-settings'
 defaults:
  _form: '\Drupal\cookiec\Forms\CookiecSettingsForm'
  _title: 'cookiec configuration'
 requirements:
  _permission: 'administer cookiec'

The name of the cookiec.settings routing is going to be placed in the info.yml file (configure: cookiec.settings), thanks to which the module configuration button will redirect us to this form.
 

Hook_init, which in D7 started every time the page loaded was removed in D8. In order to get a similar functionality, we are going to use Events, specifically EventSubscriber.
In order to create EventSubscriber in Drupal 8 we are going to need a service. We create service just like other .yml files:
Add the following to cookiec.service.yml:

services:
 cookiec_event_subscriber:
  class: Drupal\cookiec\EventSubscriber\CookiecSubscriber
  tags:
   – {name: event_subscriber}

In order for our service to be an Event Subscriber, we need to create a class implementing the interface – EventSubscriberInterface. In order for it to work correctly, we are going to create three methods:
in src/EventSubscriber/CookiecSubscriber.php

/**
 * @file Drupal\coociec\EventSubscriber\PopupMessageSubscriber
 */
namespace Drupal\cookiec\EventSubscriber;

use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;


/**
 * Class PopupMessageSubscriber
 * @package Drupal\cookiec\EventSubscriber
 */
class CookiecSubscriber implements EventSubscriberInterface {

protected $config;
 /**
  * CookiecSubscriber constructor.
  */
 public function __construct() {
  $this->config = \Drupal::configFactory()->get('cookiec.settings');
 }

 public function showCookiecMessage(FilterResponseEvent $event) {
  // Check permissions to display message.
  $response = $event->getResponse();

  if (!$response instanceof AttachmentsInterface) {
   return;
  }
 }

 /**
  * {@inheritdoc}
  */
 public static function getSubscribedEvents() {
  $events[KernelEvents::RESPONSE][] = array('showCookiecMessage', 20);

  return $events;
 }
}

Load the module settings in our constructor, which will be later passed to the JavaScript.
In order for our event to work, we have to add the getSubscribedEvents() method, put in the name of the method (showCookiecMessage) which will be called and the weight (20). We can use weight to set up the order of the events.

 public static function getSubscribedEvents() {
  $events[KernelEvents::RESPONSE][] = array('showCookiecMessage', 20);

  return $events;
 }

Our next step will be transferring over our JS and CSS files – in our case they are going to be left almost without any significant changes. Instead, we are going to focus on loading them while executing our code.
If we want to add external JS or CSS files to our module, we can create a file named module.libraries.yml, in our case this is going to be cookiec.libraries.yml

cookiec_library:
 version: 1.x
 css:
  theme:
   css/cookiec.css: {}
 js:
  js/cookiec.js: {preprocess: false}

We added cookiec.css and cookiec.js files here. If we needed to load the existing libraries, we could add them using dependencies, for example:

 dependencies:
  – core/jquery

 
In order to load our libraries now, we can use for example hook_preprocess_HOOK. In this case we have to add the following to our variables:

$variables['#attached']['library'][] = 'cookiec/cookiec_library';

In our examples we add our files right as the event loads. We are going to use the following methods to achieve this:

  $response = $event->getResponse();
  $attachments = $response->getAttachments();
  $attachments['library'][] = 'cookiec/cookiec_library';
  $response->setAttachments($attachments);

Our module does the majority of its magic client-side. To work properly, our script requires sending several parameters. Settings such as height, width, the displayed text or position are sent to drupalSettings array in JS.

  $variables = array(
   'popup_enabled' => $config->get('popup_enabled'),
   'popup_agreed_enabled' => $config->get('popup_agreed_enabled'),
   'popup_hide_agreed' => $config->get('popup_hide_agreed'),
   'popup_height' => $config->get('popup_height'),
   'popup_width' => $config->get('popup_width'),
   'popup_delay' => $config->get('popup_delay')*1000,
   'popup_link' => $config->get($language."_link"),
   'popup_position' => $config->get('popup_position'),
   'popup_language' => $language,
   'popup_html_info' => $html_info,
   'popup_html_agreed' =>$html_agreed,
  );

The values of the variables are taken from the module settings – configs. We are going to discuss this in a bit.
This is how we send our PHP variables to JS.

  $attachments['drupalSettings']['cookiec'] = $variables;

$html_info and $html_agreed store html code, obtained by parsing TWIG templates and the variables:

  $variables = array(
   'title' => 'title',
   'message' => $config->get($language."_popup_info"),
  );

  $twig = \Drupal::service('twig');
  $template = $twig->loadTemplate(drupal_get_path('module', 'cookiec') . '/templates/cookiec_info.html.twig');
  $html_info = $template->render($variables);


  $variables = array(
   'title' => 'title',
   'message' => $config->get($language."_popup_info"),
   'more' => 't(more)',
   'hide' => 't(hide)',
  );
  $twig = \Drupal::service('twig');
  $template = $twig->loadTemplate(drupal_get_path('module', 'cookiec') . '/templates/cookiec_agreed.html.twig');
  $html_agreed = $template->render($variables);

We are going to discuss TWIG in a second.

 
The entire file with EventSubscriber looks like that:

<?php

/**
 * @file Drupal\coociec\EventSubscriber\PopupMessageSubscriber
 */
namespace Drupal\cookiec\EventSubscriber;

use Drupal\Core\Language\LanguageManager;
use Drupal\Core\Render\AttachmentsInterface;
use Drupal\Core\Render\Element;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;


/**
 * Class PopupMessageSubscriber
 * @package Drupal\popup_message\EventSubscriber
 */
class CookiecSubscriber implements EventSubscriberInterface {

 /**
  * @var \Drupal\Core\Config\ImmutableConfig
  */
 protected $config;

 /**
  * PopupMessageSubscriber constructor.
  */
 public function __construct() {
  $this->config = \Drupal::configFactory()->get('cookiec.settings');
 }

 /**
  * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
  */
 public function showCookiecMessage(FilterResponseEvent $event) {
  // Check permissions to display message.
  $response = $event->getResponse();

  if (!$response instanceof AttachmentsInterface) {
   return;
  }
  // Check module has enable popup
  $config = $this->config;
  $language = \Drupal::languageManager()->getCurrentLanguage()->getId();

  $variables = array(
   'title' => 'title',
   'message' => $config->get($language."_popup_info"),
  );

  $twig = \Drupal::service('twig');
  $template = $twig->loadTemplate(drupal_get_path('module', 'cookiec') . '/templates/cookiec_info.html.twig');
  $html_info = $template->render($variables);


  $variables = array(
   'title' => 'title',
   'message' => $config->get($language."_popup_info"),
   'more' => 'more',
   'hide' => 'hide',
  );
  $twig = \Drupal::service('twig');
  $template = $twig->loadTemplate(drupal_get_path('module', 'cookiec') . '/templates/cookiec_agreed.html.twig');
  $html_agreed = $template->render($variables);

  $variables = array(
   'popup_enabled' => $config->get('popup_enabled'),
   'popup_agreed_enabled' => $config->get('popup_agreed_enabled'),
   'popup_hide_agreed' => $config->get('popup_hide_agreed'),
   'popup_height' => $config->get('popup_height'),
   'popup_width' => $config->get('popup_width'),
   'popup_delay' => $config->get('popup_delay')*1000,
   'popup_link' => $config->get($language."_link"),
   'popup_position' => $config->get('popup_position'),
   'popup_language' => $language,
   'popup_html_info' => $html_info,
   'popup_html_agreed' =>$html_agreed,
  );

  $attachments = $response->getAttachments();
  $attachments['library'][] = 'cookiec/cookiec_library';
  $attachments['drupalSettings']['cookiec'] = $variables;
  $response->setAttachments($attachments);
 }

 /**
  * {@inheritdoc}
  */
 public static function getSubscribedEvents() {
  $events[KernelEvents::RESPONSE][] = array('showCookiecMessage', 20);

  return $events;
 }
}

Another big change in Drupal 8 is the use of config files. These are also YML files, the goal of which is to facilitate synchronisation of settings, variables, and other data saved in the database between environments.
You can read more about this subject here: https://www.drupal.org/docs/8/configuration-management/managing-your-sites-configuration
The D8-compatible version of our module uses configs to store the settings of our window and to load the default settings during installation. After placing the config file in config/install folder, it is going to be automatically loaded on module installation, so we are not going to use hook_install anymore.
Our config/install/cookiec.settings.yml file”

popup_enabled: 1
popup_agreed_enabled: 1
popup_hide_agreed: 1
popup_width: 100%
popup_delay: '1'
popup_height: '100'
en_popup_title: 'Cookie policy'
en_popup_info: 'This website uses cookies. By remaining on this website you agree to our <a href="http://www.droptica.com/cookiec">cookie policy</a>'
en_popup_agreed: 'I agree'
en_popup_p_private: " <p>This website does not automatical....</p>"
pl_popup_title: 'Polityka cookie'
pl_popup_info: 'Powiadomienie o plikach cookie. Ta strona korzysta z plików cookie. Pozostając na tej stronie, wyrażasz zgodę na korzystanie z plików cookie. <a href="http://www.droptica.com/cookiec">Dowiedz się więcej'
pl_popup_agreed: 'Zgadzam się'
pl_popup_p_private: "  <p>Serwis nie zbiera w sposób automatyczny żadnych informacji, z wyjątkiem informacji zawartych w plikach cookies.</p>\r\n  <p>Pliki cookies (tzw. „ciasteczka”) </p>"
en_popup_link: /cookiec
pl_popup_link: /cookiec

If you need to load the data in the website’s code, use configFactory() service with get method and provide the name of the config.

   $this->config = \Drupal::configFactory()->get('cookiec.settings');

This code was used in the CookiecSubscriber class constructor, giving us quick and easy access to all settings of the module.
We are going to assign them to $variables array

...
 'popup_hide_agreed' => $config->get('popup_hide_agreed'),
 'popup_height' => $config->get('popup_height'),
 'popup_width' => $config->get('popup_width'),
...
 

One of the most significant changes after moving from D7 to D8 is the fact that D8 did away with PHP templates in favour of TWIG (http://twig.sensiolabs.org/). This is quite an extensive topic in itself, so we are probably going to write a separate article just about it. Right now, the main takeaway for us is that we cannot use PHP functions anymore, and the logic is limited to simple loops and conditions.
Our Drupal 7 module has two templates:
cookiec-agreed.tpl.php
cookiec-info.tpl.php

​
<?php
/**
 * @file
 * This is a template file for a pop-up informing a user that he has already
 * agreed to cookies.
 *
 * When overriding this template, it is important to note that jQuery will use
 * the following classes to assign actions to buttons:
 *
 * hide-popup-button – destroy the pop-up
 * find-more-button – link to an information page
 *
 * Variables available:
 * – $message: Contains the text that will be display within the pop-up
 */
?>

<div>
 <div class ="popup-content agreed">
  <div id="popup-text">
   <?php print $message ?>
  </div>
  <div id="popup-buttons">
   <button type="button" class="hide-popup-button"><?php print t("Hide this message"); ?> </button>
   <button type="button" class="find-more-button" ><?php print t("More information on cookies"); ?></button>
  </div>
 </div>
</div>

​

We are going to start with changing the names by replacing the extension with xxx.html.twig.
PHP tags are not parsed in .twig files, so we will have to adapt all functions and comments to the TWIG format.

Comments:

If we want to preserve old or add new comments, we have to replace the PHP tags with {# .... #}.

<#
/**
 * @file
 * This is a template file for a pop-up informing a user that he has already
 * agreed to cookies.
 *
 * When overriding this template, it is important to note that jQuery will use
 * the following classes to assign actions to buttons:
 *
 *
 * Variables available:
 * message Contains the text that will be display within the pop-up
 * hide – destroy the pop-up
 * more – link to an information page
 */
#>

Printing the variable values

We can print the variable by placing our variable in brackets like this: {{ variable }}. Our module has three variables:
message, hide, more – they contain translated strings. Adding {{ message | raw }} will cause the html to be rendered in its pure form, without for example replacing . < > into &lt; &gt; .

​
<div>
 <div class ="popup-content agreed">
  <div id="popup-text">
   {{ message | raw}}
  </div>
  <div id="popup-buttons">
   <button type="button" class="hide-popup-button"> {{ hide }} </button>
   <button type="button" class="find-more-button" > {{ more }} </button>
  </div>
 </div>
</div>

​

Logic in TWIG

Our example is fairly simple; however, TWIG allows us to use simple logic. Logic operations are wrapped between {% %} tags.
We can use tags, variable filters and functions.
Here are some examples:

Example tags:

For each loop:

  {% for user in users %}
    <li>{{ user.username|e }}</li>
  {% endfor %}

IF condition:

{% if online == false %}
  <p>Our website is in maintenance mode. Please, come back later.</p>
{% endif %}

Variable operations:

{% set foo = 'bar' %}

 
Example filters:

We use filters by adding | in {{}} brackets with our variable.

Trim – removes whitespaces or given strings.

{{ ' I like Twig. '|trim }}
{# outputs 'I like Twig.' #}

{{ ' I like Twig.'|trim('.') }}
{# outputs ' I like Twig' #}

Date – date formatting

{{ "now"|date("m/d/Y") }}
{{ post.published_at|date("m/d/Y", "Europe/Paris") }}

Functions:

random() function

{{ random(['apple', 'orange', 'citrus']) }} {# example output: orange #}
{{ random('ABC') }}             {# example output: C #}
{{ random() }}               {# example output: 15386094 (works as the native PHP mt_rand function) #}
{{ random(5) }}               {# example output: 3 #}

Drupal also has a very useful AddClass method, allowing for adding CSS classes to an html element.

{%
 set classes = [
  'red',
  'green',
 ]
%}
<div{{ attributes.addClass(classes) }}></div>

These are only a few examples and use cases, for more you can read TWIG documentation available at http://twig.sensiolabs.org/documentation.
Additionally, for working with DRUPAL 8 and TWIG it is recommended to also read:
https://www.drupal.org/node/1903374 – Debugging compiled Twig templates
https://www.drupal.org/docs/8/theming/twig/working-with-twig-templates – Working With Twig Templates
https://www.drupal.org/docs/8/theming/twig/twig-template-naming-conventions – Twig Template naming conventions

Sending data to TWIG

As you can see, TWIG offers great capabilities. However, in order for our variables to be seen in our TWIG, we have to get them there.
In our example, we have to save the content of the parsed TWIG to the variable and send it to the JS array. We can do this like that:
First, we collect the variables to be used in our TWIG:
 

$variables = array(
   'title' => 'title',
   'message' => $config->get($language."_popup_info"),
   'more' => 'more',
   'hide' => 'hide',
  );

Parsing the template:

  $twig = \Drupal::service('twig');
  $template = $twig->loadTemplate(drupal_get_path('module', 'cookiec') . '/templates/cookiec_agreed.html.twig');
  $html_agreed = $template->render($variables);

However, you will rarely use this method while working with Drupal 8. Most often you will use other methods, like overwriting the standard templates using Twig Template naming conventions.
Blocks:
1. block--module--delta.html.twig
2. block--module.html.twig
3. block.html.twig
Nodes:
1. node--nodeid--viewmode.html.twig
2. node--nodeid.html.twig
3. node--type--viewmode.html.twig
4. node--type.html.twig
5. node--viewmode.html.twig
6. node.html.twig
etc.
Then put the custom templates to our theme/templates folder.
There is also a more advanced method to do the same thing – adding files directly to the modules using hook_theme,

/**
 * Implements hook_theme().
 */
function cookiec_theme() {
 return array(
  'cookiec_agreed' => array(
   'template' => 'cookiec_agreed',
   'variables' => array(
    'title' => NULL,
    'body' => NULL,
    'read_more' => NULL,
   ),
  ),
  'cookiec_info' => array(
   'template' => 'cookiec_info',
   'variables' => array(
    'title' => NULL,
    'body' => NULL,
    'read_more' => NULL,
   ),
  ),
 );
}

In order to use such a TWIG, our block or site needs to return an array with the #theme key and the variables defined in hook_theme().
Below you can see the example of a block using a custom TWIG

namespace Drupal\hello_world\Plugin\Block;

use Drupal\Core\Block\BlockBase;

/**
 * Provides a 'Hello' Block
 *
 * @Block(
 *  id = "hello_block",
 *  admin_label = @Translation("Hello block"),
 * )
 */
class HelloBlock extends BlockBase {
 /**
  * {@inheritdoc}
  */
 public function build() {

 $variables = array(
   'title' => 'title',
   'body' => 'body',
   'read_more' => 'more',
  );

  return array(
   '#theme' => 'cookiec_info',
   '#variables' => $variables,
  );
 }
}

All functionalities were ported and are now fully compatible with Drupal 8.x. The module works perfectly and is used by us in several projects.
You can download the project from GitHub:
https://github.com/droptica/cookiec/
Leave a comment below if you have any questions or issues.
Summing up our short article: The changes between D7 and D8 are significant, and this is only a small part of the vast scope of innovations and new capabilities offered by our new CMS. If you want to further expand your knowledge about D8 and other tools useful for designing web applications, give us a like on  Facebook, where we share our tutorials, guides, and various interesting stuff from the industry. You can also take part in Drupal Day and the Drupal Camp! New content is also coming soon to our blog!
 

Large D8 logo

Dec 01 2016
Dec 01

Drupal is a bit famous for the amount of database queries done against a database. When there are thousands of concurrent users to be served, the database can quickly turn into a major bottleneck. This was the case with http://kwestiasmaku.com - a very popular website with recipes. The website is visited by millions of enthusiasts of cooking.

When we started our cooperation with KwestiaSmaku, we initially focused on adding new features and functionalities to the website. Eventually though growing popularity forced us to look at the application from a performance point of view.

Analysis

To tackle the performance bottlenecks we first wanted to understand what causes the greatest load. Usually there are two possibilities:

  1. Websites code is inefficient (it can contain obvious errors which cause additional unneeded database queries, sometimes repeated in loops and iterations).
  2. There may be no direct errors. You can tick all the boxes of building the website the Drupal way following all the best practices but with certain user volume still hit a bottleneck (usually database related).

We did not build KwestiaSmaku but took over its maintenance, so firstly we wanted to make sure that custom modules did not contain any surprises that would fit in option number one above. We used x-debug profiler and blackfire for this. These tools are very similar in many aspects but also each gives some unique options. Both allow you to observe how the page is generated and how much time is spent at each function in the call stack.

One thing we found with the tools was browsercap module, which was installed but not used for anything. It did, however, query the database substantially and for each page request. Apart from this, we found some sub-optimal things we could fix but nothing so major enough to get us out of the database bottleneck. We had to move to phase 2 - performance optimization.

One of the best tools helping you understand what happens with your server in New Relic. Data gathered by it confirmed what we suspected from the beginning- the heaviest and most time-consuming queries were the ones generated by the views module. KwestiaSmaku used fairly complex views to display lists of recipes on the front page and in categories. Views module is great but it is a two-edged sword - you can quickly create complex lists of entries but you get big and heavy database queries with multiple JOINS and nested sub-queries. 

Search API Solr

Apart from setting a time-based cache or creating a slave database and using it to feed the view, there was not much we could do to optimize database queries built by views. To work around this we proposed moving the whole view to from a database query to an index based on Apache Solr. We already used Solr to power the website's search window so the only thing we needed to do was to create another index that would hold data we needed to display on the lists of recipes.

With Solr we removed the heavy database queries, substantially decreasing the load on the database. This is clearly visible on New Relic graphs from that period. We released the change on the night from 7th to 8th November and the following day the database was already under much less stress. 

Drupal database performance optimization
Next, on 8th November we disabled the browsercap module which was still executing an inefficient query (the blue color on the graph)

Drupal database performance optimization

Thanks to the change from database to Apache Solr index we completely eliminated the most inefficient and time-consuming database queries. The longest queries are now 5 times shorter than before the change (the sharp ticks on the graphs are nightly backups)

Drupal database performance optimization

Thanks to our optimizations, kwestiasmaku.com can grow further without the need for the server change or implementation of complex server infrastructure. 

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web