[{"categories":["Development"],"contents":"You are probably interested in setting up a workign environment for Drupal-based projects or maybe you have new members in your development team, so the configuration of the correct development environment is a fundamental part of the process of working with Drupal, you are right. By reading this how-to guide, you will implement a complete and ready-to-go Drupal working environment ready for versions 8, 9, and 10 of our favorite CMS/framework. Do you want to start?\u0026hellip;\n Picture from Unsplash, user Mathyas Kurmann, @mathyaskurmann.\n This content has been constructed as a \u0026ldquo;how-to\u0026rdquo; guide, based on the Diátaxis approach for How-to guides, described by Daniele Procida.\nIndex of sections\n Introduction What you will accomplish Software requirements Set up a lightweight local environment for PHP  Get start Check your installation Check your installation   Set up an IDE for Drupal development  Get start Install VSCode Install XDebug Install PHP Codesniffer (PHPCS)   Tip: create Drupal 10 sites on the fly in your environment :wq! - Recommended song: Van Moustache - María La Portuguesa   Introduction A local development environment (or “LDE” for short) refers to the combination of software and hardware configurations necessary to develop software comfortably and productively. This includes operating systems, software for programming (IDE), programming languages, frameworks, and versioning systems.\nThe configuration of an appropriate local development environment is related to the developers\u0026rsquo; programming experience, influencing processes such as on-boarding or context-switching adaptations (when you go from programming in one language to working with another technology). As you can imagine, properly configuring LDE is necessary and very important.\nThis is even more critical when working with tools that already have a complex learning curve, just as in the case of Drupal: facilitating a local working environment is a key to starting work. Following this article, you will set up a complete LDE for Drupal, ready to use, and start your work. Happy Hacking!\nWhat you will accomplish Through the implementation of the steps recommended in this article, you will achieve the following goals:\n You will build a ready-to-go work environment. You will deploy Drupal projects based on Docker containers in LDE. You will commit code that meets quality standards from your IDE to a remote repository.  Specifically, you will have correctly configured the following environments:\n A lightweight local environment. A heavyweight local environment based on software virtualization (containers). An Integrated Development Environment (IDE) with all the necessary configurations to develop good quality code.  Software requirements Although there are no software requirements, there are operating system requirements. This how-to guide works on Ubuntu systems, specifically 20.04.6 and 22.04.1 (both LTS) and WSL, the Windows subsystem for Linux.\nTo find out your current version of Linux / Ubuntu, access the terminal and run:\nlsb_release -d This will return the description of your current version:\nAs you can see in the image above:\n lsb_release -d, getting Ubuntu 20.04.6 lsb_release -d, getting Ubuntu 22.04.1  Set up a lightweight local environment for PHP While all Drupal development relies nowadays on software virtualization environments (containers), some organizations require at least the installation of some basic PHP resources for complementary tasks, such as file validation or the execution of some functions from the terminal out of containers.\nThis implies a minimal installation on the host system. Specifically, you will install only PHP CLI, the command line tool that allows you to execute PHP scripts.\nDrupal 10 requires at least PHP 8.1, so you will have to execute different steps in Ubuntu 22.04.3 and Ubuntu Ubuntu 20.04.6.\nGet start To install PHP CLI in Ubuntu 22.04.3, follow these steps:\n  Update system dependencies:\nsudo apt update sudo apt upgrade   Install the available package for PHP 8.1, but avoiding dependencies such as Apache and other unsolicited default packages:\nsudo apt install --no-install-recommends php8.1   Install some basic PHP extensions:\nsudo apt-get install -y php8.1-cli php8.1-common php8.1-zip php8.1-gd php8.1-mbstring php8.1-curl php8.1-xml php8.1-bcmath   Now, it\u0026rsquo;s time for the basic installation of PHP on an older LTS version of Ubuntu. In this case, the PHP version available in the official repositories is still PHP 7.4.3, so to align it to the versions required by Drupal 10, we will have to make some adjustments.\nTo install PHP CLI in Ubuntu 20.04.6, follow these steps:\n  Update system dependencies:\nsudo apt update sudo apt upgrade   You will use the reference repository of Ondřej Surý for PHP versions, so add a new \u0026ldquo;Personal Package Archive\u0026rdquo; (PPA) as a new available repository in your system:\nsudo add-apt-repository ppa:ondrej/php sudo apt update And press ENTER when prompted.\n  Now install the required PHP versions:\nsudo apt install php8.1   Finally, install some basic PHP extensions:\nsudo apt install -y php8.1-cli php8.1-common php8.1-zip php8.1-gd php8.1-mbstring php8.1-curl php8.1-xml php8.1-bcmath php8.1-sqlite3   The purpose of these lightweight installations of PHP resources in the local environment is to serve as an \u0026ldquo;extra tool\u0026rdquo; for working with PHP files.\nCheck your installation Now, you must perform some basic checks to confirm that everything is working well. To test your local PHP installation, follow these steps:\n  Check your PHP modules installation:\nphp -m This will return a complete list of PHP and Zend modules installed on your systen, including basic resources as gd (image graphics library), mbstring (multibyte enconding) or Zend OPcache (objects cache).\n  Create a simple PHP file:\ncat \u0026gt; phpinfo.php \u0026lt;?php phpinfo(); ?\u0026gt; And exit from text editor typing CTRL+D in prompt.\n  Execute the PHP file using the PHP built-in web server:\nphp -S localhost:8000 phpinfo.php   Open the URL in your favourite browser and get data from your PHP local installation:\n  Prepare an on-the-fly Drupal installation following the steps recommended in the Quick Start documentation:\ncurl -sSL https://www.drupal.org/download-latest/tar.gz | tar -xz --strip-components=1 You may encounter permissions issues from the execution of tar command running the above recommended command. In that case, try running it:\nwget -c https://www.drupal.org/download-latest/tar.gz -O - | sudo tar -xz But you will need to change owner and permissions for the new folder:\nsudo chown -R $USER:$USER drupal-* sudo chmod -R 775 drupal-*   Launch a Drupal installation:\ncd drupal-* php -d memory_limit=256M ./core/scripts/drupal quick-start standard --site-name QuickInstall --host localhost --port 8080   Check out the new Drupal site automatically created and available in a tab of your preferred web browser:\n  For more information about the PHP built-in web server, read the PHP documentation page.\nSet up a heavyweight local environment based on software containers Now, it is time to prepare installation of a suitable local working environment related to the trend established in recent years: container-based (Docker and related resources). For this, we will install Docker as the base system for container management, and on this platform, we will install DDEV.\nAs for Docker, in 2023-2024, there is little left to say: the de-facto standard for software virtualization based on the concept of \u0026ldquo;containers\u0026rdquo; and the base concept for other DevOps technology stacks currently in extensive use.\nWe have already talked about DDEV in other articles, posts, tutorials and how-to guides: a solution running on Docker for PHP-based web platforms that facilitates the execution of projects (previously existing and new ones). Why DDEV? Compared to other container-based tools such as Docker4Drupal, Lando, or Docksal, DDEV has recently gained significant support from the Drupal community, which makes it almost already the chosen option for development.\nRead more about DDEV as solution:\n About how DDEV works, read \u0026ldquo;Creating development environments for Drupal with DDEV\u0026rdquo;. About DDEV reading materials, read \u0026ldquo;Books: Local Web development with DDEV (Review)\u0026quot;. About DDEV installation, read the \u0026ldquo;DDEV documentation\u0026rdquo;. About how to develop a Drupal site on your local machine using Docker and DDEV, read \u0026ldquo;How To Develop a Drupal 9 Website on Your Local Machine Using Docker and DDEV (Digital Ocean)\u0026quot;. For a quick cheatsheet (and nowadays slightly outdated), read: Tooling: Docker Docker-Compose DDEV - Cheatsheet.  Get start To install Docker and DDEV in your local environment, follow the next steps:\n  Install Docker and its resources:\nsudo apt update sudo apt -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin   Add DDEV\u0026rsquo;s GPG key to your keyring:\ncurl -fsSL https://pkg.ddev.com/apt/gpg.key | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/ddev.gpg \u0026gt; /dev/null   Add DDEV releases to your package repository:\necho \u0026quot;deb [signed-by=/etc/apt/trusted.gpg.d/ddev.gpg] https://pkg.ddev.com/apt/ * *\u0026quot; | sudo tee /etc/apt/sources.list.d/ddev.list   Now update info and install DDEV:\nsudo apt update sudo apt install -y ddev   Confirm the installation of the software by checking version:\nddev -v You will get from prompt something like:\nddev version v1.22.1   Check your installation To test your local DDEV installation, follow these steps:\n  Create a new Drupal 10 site:\nPrepare the main folder:\nmkdir drupal10-site \u0026amp;\u0026amp; cd drupal10-site   Enable the basic configuration for the site:\nddev config --project-type=drupal10 --create-docroot --docroot=web   Init the DDEV container ecosystem:\nddev start   Build the new site downloading basic resources and executing the installation:\nddev composer create drupal/recommended-project \u0026amp;\u0026amp; \\ ddev composer require drush/drush \u0026amp;\u0026amp; \\ ddev drush site:install --account-name=admin --account-pass=admin -y   Finally, launch the new Drupal site:\nddev drush uli | xargs xdg-open   You can run a DDEV command to show the site:\nddev launch   Set up an IDE for Drupal development As a third step, you will install an integrated development environment (IDE). An IDE is a fundamental tool for software development, and in the case of Drupal, you will have to make some adaptations to work with its code.\nIn this scenario you will work with VSCode, Microsoft\u0026rsquo;s IDE tool released as Open Source that has a 100% free alternate build (VSCodium, which does not integrate Microsoft\u0026rsquo;s usage telemetry).\nYou will install the IDE on Linux (Ubuntu / Debian-based) and then you will make the necessary configurations and custom changes. To prepare an IDE in your local environment, follow the steps below:\nGet start To have a fully functional environment, follow the steps below.\nInstall VSCode  Download VSCode for your OS (Ubuntu / Debian based) from https://code.visualstudio.com/download. Move to your Downloads folder and install the .deb file: cd ~/Downloads sudo apt install ./\u0026lt;file_name\u0026gt;.deb   Install XDebug   Open your VSCode installation and launch VSCode Quick Open (Ctrl+P).\n  Install the PHP Debug extension for VSCode typing the next command in the new box and press enter:\next install xdebug.php-debug ![Installing PHP Debug extension in VSCode](../../images/post/davidjguru_drupal_how_to_set_up_a_local_development_environment_6.jpg)    Prepare a launch.json file per project\u0026rsquo;s folder, including the lines for Xdebug connections:\n { \u0026quot;version\u0026quot;: \u0026quot;0.2.0\u0026quot;, \u0026quot;configurations\u0026quot;: [ { \u0026quot;name\u0026quot;: \u0026quot;Listen for Xdebug\u0026quot;, \u0026quot;type\u0026quot;: \u0026quot;php\u0026quot;, \u0026quot;request\u0026quot;: \u0026quot;launch\u0026quot;, \u0026quot;hostname\u0026quot;: \u0026quot;0.0.0.0\u0026quot;, \u0026quot;port\u0026quot;: 9003, \u0026quot;pathMappings\u0026quot;: { \u0026quot;/var/www/html\u0026quot;: \u0026quot;${workspaceFolder}\u0026quot; } } ] }   Enable XDebug in ddev, just run:\nddev xdebug on   Now you can enable the debug mode by clicking in option \u0026ldquo;Run and Debug\u0026rdquo;, then put some breakpoints whitin the source code and try to run the site. You are ready for debugging.\n  Install PHP Codesniffer (PHPCS) PHP CodeSniffer (PHPCS) is a pair of scripts (phpcs and phpcbf) to detect violations and perform automatic repairs of coding standards. This integration requires some tasks:\n  Install PHPCS as a resource in DDEV containers:\nddev composer require --dev drupal/coder dealerdirect/phpcodesniffer-composer-installer   Verify PHPCS has been installed in your DDEV-based Drupal site:\nddev exec vendor/bin/phpcs -i Your should see something like:\nThe installed coding standards are MySource, PEAR, PSR1, PSR2, PSR12, Squiz, Zend, Drupal, DrupalPractice, VariableAnalysis and SlevomatCodingStandard This is the list of coding standards enabled for code sniffing.\n  Enable version control in project root (if not already done) and create a new folder for scripting:\ncd $PROJECT_ROOT git init mkdir -p scripts/git/pre-commit   Create a new pair of scripts in the folder, pre-commit and pre-commit-phpcs.php with content:\n#!/bin/sh # Run pre-commit check PHP script inside ddev when committing from host. if [ \u0026quot;$IS_DDEV_PROJECT\u0026quot; != true ]; then ddev exec /usr/bin/php scripts/git/pre-commit-phpcs.php else /usr/bin/php scripts/git/pre-commit-phpcs.php fi and:\n\u0026lt;?php /** * @file * A Git pre-commit script to check files for PHP syntax errors. * * Requires a properly configured phpcs.xml in your codebase. * * Inspired from https://www.drupal.org/project/dcq * and modified for Drupal 8+ and for running **inside** DDEV. */ $exit_code = 0; $files = []; // Determine if is the first commit or not. exec('git rev-parse --verify HEAD 2\u0026gt; /dev/null', $files, $return); $against = ($return == 0) ? 'HEAD' : '4b825dc642cb6eb9a060e54bf8d69288fbee4904'; // Identify changed files. exec(\u0026quot;git diff-index --cached --name-only $against\u0026quot;, $files); print \u0026quot;\\nPrecommit PHPCS\\n\\n\u0026quot;; foreach ($files as $file) { if (file_exists($file) \u0026amp;\u0026amp; !is_dir($file)) { // Perform PHP syntax check (lint). $return = 0; $lint_cmd = \u0026quot;php -l {$file}\u0026quot;; $lint_output = []; exec($lint_cmd, $lint_output, $return); if ($return !== 0) { // Format error messages and set exit code. $exit_code = 1; } // Perform phpcs test. $return = 0; $phpcs_cmd = 'phpcs ' . $file; $phpcs_output = []; exec($phpcs_cmd, $phpcs_output, $return); if ($return !== 0) { // Format error messages and set exit code. echo implode(\u0026quot;\\n\u0026quot;, $phpcs_output), \u0026quot;\\n\u0026quot;; $exit_code = 1; } } } exit($exit_code);   Connect PHPCS with git commits to perform code reviews before submitting to repository.\nchmod +x scripts/git/pre-commit cd .git/hooks \u0026amp;\u0026amp; ln -s ../../scripts/git/pre-commit Now, every time you commit a new change, git will identify the newly modified files and if applicable (within the PHPCS configuration rules), it will perform a code review, giving you feedback.\n  Add new configuration rules for PHPCS: Create a new PHPCS config file in root folder, the new phpcs.xml.dist will contain:\n \u0026lt;?xml version=\u0026quot;1.0\u0026quot; encoding=\u0026quot;UTF-8\u0026quot;?\u0026gt; \u0026lt;ruleset name=\u0026quot;drupal_website_development\u0026quot;\u0026gt; \u0026lt;description\u0026gt;PHP CodeSniffer configuration for Drupal website development.\u0026lt;/description\u0026gt; \u0026lt;arg name=\u0026quot;extensions\u0026quot; value=\u0026quot;yaml,yml,php,inc,module,install,info,test,profile,theme,css,js\u0026quot;/\u0026gt; \u0026lt;arg name=\u0026quot;report\u0026quot; value=\u0026quot;full\u0026quot;/\u0026gt; \u0026lt;arg value=\u0026quot;p\u0026quot;/\u0026gt; \u0026lt;arg name=\u0026quot;colors\u0026quot;/\u0026gt; \u0026lt;!--Include custom code.--\u0026gt; \u0026lt;file\u0026gt;RoboFile.php\u0026lt;/file\u0026gt; \u0026lt;file\u0026gt;web/modules/custom\u0026lt;/file\u0026gt; \u0026lt;file\u0026gt;web/themes/custom\u0026lt;/file\u0026gt; \u0026lt;!--Exclude third party code.--\u0026gt; \u0026lt;exclude-pattern\u0026gt;./.ddev\u0026lt;/exclude-pattern\u0026gt; \u0026lt;exclude-pattern\u0026gt;./vendor\u0026lt;/exclude-pattern\u0026gt; \u0026lt;exclude-pattern\u0026gt;./web/core\u0026lt;/exclude-pattern\u0026gt; \u0026lt;exclude-pattern\u0026gt;./web/libraries\u0026lt;/exclude-pattern\u0026gt; \u0026lt;exclude-pattern\u0026gt;./web/modules/contrib\u0026lt;/exclude-pattern\u0026gt; \u0026lt;exclude-pattern\u0026gt;./web/themes/contrib\u0026lt;/exclude-pattern\u0026gt; \u0026lt;exclude-pattern\u0026gt;./web/sites\u0026lt;/exclude-pattern\u0026gt; \u0026lt;!--Exclude Drupal generated config files.--\u0026gt; \u0026lt;exclude-pattern\u0026gt;./config\u0026lt;/exclude-pattern\u0026gt; \u0026lt;rule ref=\u0026quot;Drupal\u0026quot; /\u0026gt; \u0026lt;rule ref=\u0026quot;DrupalPractice\u0026quot; /\u0026gt; \u0026lt;/ruleset\u0026gt;   The new file will provide the basic enabled rules for PHPCS. You can find more inspiration and examples in others phpcs.xml.dist files, such as the one in the Drupal core, path web/core/phpcs.xml.dist. Don\u0026rsquo;t forget to put this new file under git control and commit it to repository.\n Read more tutorials and guides about configure Xdebug in differents tools. Read more about launch configurations in VSCode: code.visualstudio.com/docs/editor/debugging#_launch-configurations. Read more about how to configure Xdebug in VSCode for DDEV: ddev.readthedocs.io/visual-studio-code-vs-code-debugging-setup. Read more about advanced usage of PHP Codesniffer: PHP_CodeSniffer/wiki/Advanced-Usage. Read more about recommended extensions for Drupal in VSCode: www.drupal.org/recommended-extensions-for-drupal.  Tip: Create Drupal 10 sites on the fly in your environment Create bash functions to launch Drupal 10 web sites on the fly from your terminal. Now you can reuse common steps to save repetitive tasks in your system, for example creating new Drupal 10 sites to test features.\nTo create Drupal 10 websites in an automated way, follow the steps below:\n  Stop Apache in your environment, this will free port 80:\n/etc/init.d/apache2 stop   Create (if it does not exist) a .bash_functions file in your home directory:\nvim ~/.bash_functions   Add a specific block with some bash commands, gathering all the related DDEV commands to create a new Drupal 10 site:\n## Creating Drupal projects by using DDEV. d10ddev () { # If you don't provide a name the script will get one random for the site. if [ -z \u0026quot;$1\u0026quot; ] then check=$(shuf -n1 /usr/share/dict/words) shortened=${check::-2} varkeyname=${shortened,,} else varkeyname=$1 fi # Create main project folder. mkdir $varkeyname \u0026amp;\u0026amp; cd $varkeyname # Prepare basic configuration. ddev config --project-type=drupal10 --docroot=web --create-docroot yes | ddev composer create \u0026quot;drupal/recommended-project:^10\u0026quot; # Require some extra Drupal resources. ddev composer require drush/drush drupal/admin_toolbar drupal/devel drupal/coffee ddev composer update --lock # Execute site install. ddev exec drush si --site-name=$varkeyname --account-name=admin --account-pass=admin -y # Enable modules and clean cache. ddev drush en -y admin_toolbar admin_toolbar_tools admin_toolbar_search admin_toolbar_links_access_filter devel devel_generate coffee ddev drush cr # Start the new site and open it in browser. ddev start \u0026amp;\u0026amp; ddev launch }   Edit your main .bashrc file and make sure you have a block like this (if not, add the lines):\n# Alias definitions. # You may want to put all your additions into a separate file like # ~/.bash_aliases, instead of adding them here directly. # See /usr/share/doc/bash-doc/examples in the bash-doc package. if [ -f ~/.bash_aliases ]; then . ~/.bash_aliases fi if [ -f ~/.bash_functions ]; then . ~/.bash_functions fi   Source the .bashrc file to make the changes take effect:\nsource ~/.bashrc   Now you can create new Drupal 10 sites on the fly, just run:\nd10ddev   To delete dummy Drupal sites, just add a new fuction in the .bash_functions file:\n## Destroy enabled Drupal site based on DDEV by name from project folder. ddevdestroy () { varkeyname=${PWD##*/} ddev stop yes |ddev delete -O cd .. rm -rf $varkeyname } This will stop the containers network, destroy the DDEV containers and finally delete the source code from the project folder.\n  Get some examples of bash scripting for day-to-day use in Drupal / DDEV based projects here in GitHub.\n  Read more about how to customize bashrc files.\n  :wq! That\u0026rsquo;s it! congratulations, if you have followed all the steps in this how-to guide, then you have completed a local development environment for Drupal. I leave you with a final song, which you can find in the Spotify playlist \u0026ldquo;The Russian Lullaby\u0026rdquo;.\nSee you!\nRecommended song: Van Moustache - María La Portuguesa   ","permalink":"https://www.therussianlullaby.dev/blog/how-to-set-up-a-local-development-environment-for-drupal/","tags":["Environments","Drupal Development","Backend","PHP"],"title":"How to set up a local development environment (LDE) for Drupal"},{"categories":["Development"],"contents":"You\u0026rsquo;ve probably heard of \u0026ldquo;decoupled Drupal\u0026rdquo; or \u0026ldquo;headless\u0026rdquo;, a way of working with Drupal that consists of separating the frontend from the backend, implementing communication between both parts as separate projects, distinct repositories, etc. Usually, this way of working necessarily involves \u0026ldquo;breaking the monolith\u0026rdquo; i.e. splitting into several parts of different technology, leaving Drupal only for the backend side of the project. In this context, you will use some kind of specification or API connection to make backend and frontend work well and understand each other. You can use REST, JSON:API or GraphQL as well. This article is oriented to this last opinion, the possibility of using GraphQL in Drupal, and is edited and published as the first post of a series about GraphQL. As an introduction, I would like to share some initial thoughts based only on my observations.\n Picture from Unsplash, user Masjid Pogung Dalangan, @masjidmpd.\n Index of sections\nIntroduction Fast Rewind 1- Align file naming and resources 2- Respect the order of parameters 3- Enable debugging mode 4- Maintain your code clean and do refactoring 5- GraphQL may not be your best option 6- Read More :wq!\n Introduction I have been working on openly decoupled Drupal projects for some time now, and I would like to share some experiences. I\u0026rsquo;m not a GraphQL advocate, I think there are already many people and many platforms that do it quite well, so I\u0026rsquo;m not interested in holy wars. In fact I wouldn\u0026rsquo;t convince you to use GraphQL in your project, but there are situations where someone may be conditioned by circumstances to work with GraphQL. So I have taken as an excuse the intention of publishing five basic ideas about GraphQL in Drupal. As an introductory article, but taking advantage of some initial notes.\nGraphQL was intended as a way to avoid overfetching in other approaches such as REST APIs: you describe exactly what resources you will expose and how you want to expose them. You then build specific queries that will scrupulously respect these descriptions and fetch the exact data that has been requested. In this article we will take a brief look at GraphQL by way of introduction: installation, features, aspects to take into account, possible limitations and an index of recommended reading.\nThere we go!\nFast Rewind GraphQL is an open-source data query and manipulation language for APIs developed by Facebook in 2012 and publicly released in 2015. Now the GraphQL project is hosted by the Linux Foundation (see the projects protected under the Linux Foundation umbrella here).\nCommon Terms\n Schema: A Schema is a description of resources that will be exposed via GraphQL. Server: A server is the URL where a schema will be accessible. graphqls: The file extension for descriptive schema files in GraphQL. Resolvers: Are collections of functions that build a response for a GraphQL input query, acting like a GraphQL query handler. Data Producers: A data producer is function taking arguments and processing these inputs into some other data. GraphiQL Explorer: Visual tool for execute and review results of your query, your blackboard.  Installing GraphQL in Drupal In former versions of Drupal (Drupal 8 basically) you could execute:\n$ composer require drupal/graphql And everything goes well, but from Drupal 9 you will get (by now):\n$ composer require drupal/graphql Your requirements could not be resolved to an installable set of packages. Problem 1 - Root composer.json requires drupal/graphql ^4.2 -\u0026gt; satisfiable by drupal/graphql[4.2.0]. - drupal/graphql 4.2.0 requires drupal/typed_data * -\u0026gt; found drupal/typed_data[dev-1.x, 1.0.0-alpha1, ..., 1.x-dev (alias of dev-1.x)] but it does not match your minimum-stability. In fact, I\u0026rsquo;m trying to install the last available version for drupal/graphql, 4.3.0 (released on 15/02/2022) in Drupal 9.3.6 and I\u0026rsquo;m facing the same problems related to the stability of the drupal/typed_data version:\nSo first you have to install the required dependency in alpha versions and then GraphQL. I wrote some notes about this here in my guide for Drupal upgrading, troubleshooting section.\nJust:\n$ composer require drupal/typed_data:@alpha [...] $ composer require drupal/graphql And now everything goes well. When you finish, you have to enable the new module by drush:\n$ drush en -y graphql For learning purposes, please enable the secondary modules (with available examples) too:\n$ drush en -y graphql graphql_composable graphql_examples After login, you can navigate to /admin/config/graphql and create a new server. You can use the \u0026ldquo;Example schema\u0026rdquo; that comes with the graphql_examples module (as we can see in the former step the examples comes with the graphql module but needs to be enabled separately).\nAfter creating the server click on dropdown button at right, click in \u0026ldquo;Explorer\u0026rdquo; option and this should bring you to the GraphiQL explorer, or you can go directly to /admin/config/graphql/servers/manage/initial_example/explorer and there you will see the visual explorer GraphiQL for visualizing queries within your Drupal installation. This will be your new best friend, the place where you will live from now on:\nYou can use some plugins for IDEs too, like the graphiql-explorer for VSCode. If you don\u0026rsquo;t have much experience in the GraphQL - Drupal combination, you can review the source code of this contributed module and the folder structure with the resources, within your project\u0026rsquo; folder or within its own gitlab repository.\n1- Align file naming and resources Well, the first small idea to share (small but very useful), is this: always keep the names of the internal resources of each module aligned. This will make your life much easier and will make you happy, for sure, reducing the time to search for errors without results.\nAt the beginning it will quite simple but the more your project grows, the more GraphQL custom modules it will contain with more files, with more resources and with different names. It will be easy to start a new GraphQL module by copying and pasting files from a previous module and there\u0026hellip; you may forget to change names.\nMy advice:Before the GraphQL dimension of your project grows, make naming patterns for custom resources. See the next screen caption: Ok, as you can see when you\u0026rsquo;re working with GraphQL in Drupal you have to create new custom resources, new custom modules containing all the required files for extending your current Schema and defining new types, fields, queries\u0026hellip; See the former image: think about your folder structure and get a naming pattern, something like:\n graphql_custom_name/ | | | | | |_ _ graphql/ | | | | | |_ _ custom_name.base.graphqls | | |_ _ custom_name.extension.graphqls | | | |_ _ _ src/Plugin/GraphQL/ | | | |_ _ DataProducer/ | | \\ | | \\_ _ CustomName.php | | | |_ _ SchemaExtension/ | \\ | \\_ _ CustomNameSchemaExtension.php | |_ _ _ _ graphql_custom_name.info.yml This can be important to delay chaos within the project. Especially if not only you work with GraphQL within the project. Go ahead and propose to your colleagues naming patterns that you can use comfortably.\n2- Respect the order of parameters Another basic aspect but one that can take up a lot of your time\u0026hellip; please\u0026hellip;\nMy advice:Just keep the order of the parameters in the same sense that you expect it in your DataProducer, ok? See the following image: As you can see in the image above, first you\u0026rsquo;ve described a new extension for a Query type, in this example is for querying \u0026ldquo;News\u0026rdquo; in our Drupal installation. Then you\u0026rsquo;re adding a new Schema Extension, adding FieldResolvers for the new query and you are using \u0026ldquo;query_news\u0026rdquo; as your custom DataProducer. In this declaration to register, you\u0026rsquo;re sending the parameters in the order:\ncollection_id =\u0026gt; Identifier of the collection to query. Taken as an incoming argument from the query parameters. offset =\u0026gt; Starting point for the query of items. Taken as an incoming argument from the query parameters. limit =\u0026gt; How many items do you want to get. Taken as an incoming argument from the query parameters. language =\u0026gt; The language code of the requested items. Taken as an incoming argument from the query parameters. All these parameters will function as filters within our custom DataProducer. If I don\u0026rsquo;t respect the order of the parameters in the resolve method definition itself in my custom DataProducer, then I will get strange results. In the bottom tab of the image above you can see the values when debugging with Xdebug: we were not obtaining the required items, and we were not getting the expected values since what should be the offset (zero value), here is working as a limit (zero items to return). This may cause you a little headache and make you debug more than necessary, just for a matter of order in the parameters passed to the custom data producer class.\n3-Enable debugging mode Do you know the phrase \u0026ldquo;the future is where you will spend the rest of your life\u0026rdquo;? Well, in GraphQL\u0026rsquo;s case, debugging mode will be the place for the rest of your life. You will have to define a lot of schemas, declare a lot of types, build a lot of queries and implement some custom Data Producers\u0026hellip;so the third small tip has to do with a quick action that will undoubtedly offer you great benefits.\nMy advice:Enabling debug mode for your GraphQL environment will be essential. This, coupled with enabling Xdebug for debugging from the PHP side, will make any implementation that gets stuck along the way much easier to deal with. Enable debug to get exception specific messages going to /admin/config/graphql/servers/manage/ and edit the configuration of your schema server. You will be very happy.\nRead More about enabling Xdebug for Drupal:\n Introduction to Xdebug for DDEV Drupal Techniques: Xdebug, DDEV and Postman Configuration for Debugging Decoupled Drupal using Lando  4- Maintain your code clean and do refactoring In the middle of working on a decoupled Drupal project can be common to define needs as you go along, on the fly. For example, from the frontend, it may be necessary to show a new listing.\n Then frontend asks backend: I need a new list of items (News). Backend responds by creating a custom data producer (if none fits well). Backend prepares a query extracting the required data:  try { $node_storage = $this-\u0026gt;entityTypeManager-\u0026gt;getStorage('node'); $type = $node_storage-\u0026gt;getEntityType(); // Extracting main query. $query = $node_storage-\u0026gt;getQuery() -\u0026gt;currentRevision() -\u0026gt;accessCheck(); $query -\u0026gt;condition('status', TRUE) -\u0026gt;condition('langcode', $language) -\u0026gt;condition('field_related', $field_value) -\u0026gt;condition('type', ['type_one','type_two', 'type_three'], 'IN') -\u0026gt;sort('created', 'DESC'); // Add cache info. $metadata-\u0026gt;addCacheTags($type-\u0026gt;getListCacheTags()); $metadata-\u0026gt;addCacheContexts($type-\u0026gt;getListCacheContexts()); // Get the array of node ids and process the required info. $node_ids = $query-\u0026gt;execute(); $available_nodes = $node_storage-\u0026gt;loadMultiple($node_ids); } catch (\\Exception $e) { return []; }  Then, backend will prepare the data filtering:  // Processing the resulting array of total nodes. // First reduce the items cutting by offset and limit. // Second allows only nodes with moderation_state as published. $filtered_array = array_filter($available_nodes, function ($node){ // @see https://www.drupal.org/project/drupal/issues/3025164 return ( $node-\u0026gt;get('moderation_state')-\u0026gt;value == 'published'); }, TRUE); And finally we\u0026rsquo;ll prepare the array of values to return from our custom Data Producer:\n// Prepare the array of page nodes (Type 1, Type 2, Type 3, Type 4). $news = [ ]; foreach ($filtered_array as $node) { $id = $node-\u0026gt;id(); $final_url = $node-\u0026gt;toUrl()-\u0026gt;toString(TRUE); $url_string = $final_url-\u0026gt;getGeneratedUrl(); $pages[] = [ 'id' =\u0026gt; $id, 'preamble' =\u0026gt; $node-\u0026gt;get('field_summary')-\u0026gt;value, 'title' =\u0026gt; $node-\u0026gt;title-\u0026gt;value, 'url' =\u0026gt; $url_string, 'bundle' =\u0026gt; $node-\u0026gt;bundle(), ]; } return $news; } But then times goes by, the project progresses, other tickets are solved and the frontend colleagues start asking other things for the same query (or maybe yourself if you\u0026rsquo;re working in full-stack mode). Anyway, it produces a request \u0026ldquo;Hey, I need to add to the query a value of items returned and the total number of available existing items in database, could you implement it, please?\u0026rdquo; and it\u0026rsquo;s time to return to the code you left in that class.\nIn your frantic life, the request for a calculation of available values will be translated as \u0026ldquo;I must run a count query.\u0026rdquo; and you will add in a hurry, a new query in code:\n$query = $node_storage-\u0026gt;getQuery() -\u0026gt;currentRevision() -\u0026gt;accessCheck(); $query -\u0026gt;condition('status', TRUE) -\u0026gt;condition('langcode', $language) -\u0026gt;condition('field_related', $field_value) -\u0026gt;condition('type', ['type_one','type_two', 'type_three'], 'IN') -\u0026gt;sort('created', 'DESC'); $existing_items = $query-\u0026gt;count()-\u0026gt;execute(); Ok? Nopes, \u0026lsquo;cause you already have a previous query and with some adjustments you can make, you will get the data you need for this new request. With a little refactoring your code can evolve better. If you don\u0026rsquo;t try to think like that, your classes will grow out of control and in a short time they will start to become incomprehensible and out of logic. Instead of adding more logic, try to think in something like this, just after execute the first query:\n// Processing the resulting array of total nodes. // First reduce the items cutting by offset and limit. // Second allows only nodes with moderation_state as published. $processed_array = array_slice($available_nodes, $offset, $limit, TRUE); $filtered_array = array_filter($processed_array, function ($node){ // @see https://www.drupal.org/project/drupal/issues/3025164 return ( $node-\u0026gt;get('moderation_state')-\u0026gt;value == 'published'); }, TRUE); // Load the values for available and returned items. $batch = [ 'total' =\u0026gt; count($available_nodes), 'items' =\u0026gt; count($filtered_array), ]; And then do the data processing from the returned set of nodes:\n// Prepare the array of page nodes (Type 1, Type 2, Type 3, Type 4). $news = []; foreach ($filtered_array as $node) { $id = $node-\u0026gt;id(); $final_url = $node-\u0026gt;toUrl()-\u0026gt;toString(TRUE); $url_string = $final_url-\u0026gt;getGeneratedUrl(); $pages[] = [ 'id' =\u0026gt; $id, 'preamble' =\u0026gt; $node-\u0026gt;get('field_summary')-\u0026gt;value, 'title' =\u0026gt; $node-\u0026gt;title-\u0026gt;value, 'url' =\u0026gt; $url_string, 'bundle' =\u0026gt; $node-\u0026gt;bundle(), ]; } // Finally load the batch of nodes. $batch['news'] = $news; My advice:Keep your code in good shape. Think about You\u0026rsquo;re adding new data by extending more and more values and sections in your returned array of data: So please, remember: review and revise the code above. Don\u0026rsquo;t add more code than you need. Use the calculations you have already done and if necessary, change and refactor. Avoid unnecessary growth of your DataProducer classes.\n5- GraphQL may not be your best option Despite the enormous flexibility GraphQL offers, and the optimization involved in building your own schemas and queries, it is not all advantages: you can run into important problems and limitations. Intuitively, we can say that GraphQL may not be a good option if your team is small or your budget is tight. In addition, I have gathered three more points to help you think through whether it is the right solution:\n  Documentation is scarce and may not be very up to date. You have few resources available to learn. You can go to www.drupal.org/docs/graphql and then you will see that the information may be very little, outdated or non-existent. This is not very motivating and you will have to build a curated reading list on your own. Of course, the Amazee Labs content list related to GraphQL is fundamental. It is arguably the company that has written the most about the Drupal \u0026amp; GraphQL partnership. You have the links in the last section, Read More.\n  You will have to expose everything-everything to your frontend. This usually includes providing all the navigation: menus, links, sections, paths, content: titles, bodies, taxonomy terms and other available fields. Do you use search subsystems? searching-indexes? elastic search? you\u0026rsquo;ll have to expose and connect every resource. And all the structural and meta-information for SEO: Blocks, sections, layout regions information, metatags, descriptions, etc. This can increase the workload of a project exponentially. It may not be your option if you don\u0026rsquo;t have the right knowledge, the right budget, the time required or if your team is small.\n  You will have to operate from scratch. As with other external connection resources (REST, JSON API), GraphQL for Drupal used to expose all existing resources in the Drupal installation by default. You could simply install the module and make all entities available through GraphQL. This changed between versions 3 and 4, and that default exposure is no longer available. Now users must manually describe schemas for every entity and resource they need. You can see the discussion here. The practical consequence is that you may need even more time and work to build all the schemas you need.\n  6- Read More  Amazee Labs: GraphQL Introduction Amazee Labs: Drupal and GraphQL with React and Apollo Amazee Labs: Drupal and GraphQL - Batteries included Amazee Labs: Extending GraphQL Part 1 - Fields Amazee Labs: Extending GraphQL Part 2 - Types and Interfaces Amazee Labs: Extending GraphQL Part 3 - Mutations Amazee Labs: GraphQL for Drupalers Part 1 - The Basics Amazee Labs: GraphQL for Drupalers Part 2 - The Queries Amazee Labs: GraphQL for Drupalers Part 3 - The Fields Amazee Labs: GraphQL for Drupalers Part 4 - Fetching the entities Amazee Labs: Don\u0026rsquo;t push it - Using GraphQL in Twig  Others\n Valuebound: GraphQL a Beginners Guide (2018) Specbee: GraphQL with Drupal 8: All you need to know (2019) OpenSense Labs: Why is GraphQL an Importante Player in Decoupled Drupal? (2020) Mediacurrent: A Recipe for a Graphql Server in Drupal Using graphql-php (2020)  :wq! If you have managed to reach the end of this article, Thank you very much! Thanks for your patience and I really hope it has been useful to you. I wish you good luck working with Drupal and GraphQL so you will learn a lot (I\u0026rsquo;m doing it now). I leave you with a final song, which you can find in the Spotify playlist \u0026ldquo;The Russian Lullaby\u0026rdquo;.\nSee you next time!\nRecommended song: The Troublemakers - Get Misunderstood   ","permalink":"https://www.therussianlullaby.dev/blog/five-basic-things-ive-learned-using-graphql-in-drupal/","tags":["GraphQL","Drupal Development","Backend","PHP"],"title":"Five basic things I've learned using GraphQL in Drupal"},{"categories":["Reading"],"contents":"Of all the APIs available in Drupal, the Migrate API may be one of the most hidden from the average Drupal user. It is even possible that, while reading this post, you discover for the first time that there is a whole set of Drupal modules, both core and contributed, fully dedicated to designing and running migration processes inside Drupal. You can move data from an old Drupal site to the most recent version, but also from other databases, files, systems, frameworks, CMSs, DXPs\u0026hellip; and all of these options come together around the Drupal Migrate API. It is a set of resources that demands real dedication, partly because the available information has been so fragmented until now. The book I want to discuss today brings order and unity to all this. Pay attention.\n Picture from Unsplash, user Will Paterson, @willpat\n Table of Contents\n1- Introduction 2- The Book 3- Recommendations 4- Book Information 5- Fast Review 6- Ratings 7- :wq!\n 1- Introduction Migration processes are fairly common in project development: it is quite normal to build a new Drupal-based website for a client that already has a platform implemented in a different technology (not kidding, this happens). As part of these processes, a client may need to move their data to the new platform and adapt it to Drupal\u0026rsquo;s data model, with its own tables and relationships.\nIn these situations, several scenarios open up for these processes: the study of the origin of the data, the processing possibilities, the methodology to sanitize the information and how to store it in a stable way in our new environment. What I have just briefly described is what is involved in an ETL process: Extraction - Transformation and Load for the data migration, and this is the spirit of the book I am discussing today: the exhaustive study of the design and execution for ETL process in Drupal.\nFor those of us who at some point have had to learn to migrate data to Drupal by the hard way, I\u0026rsquo;m sure it has been recurrent a) to search for information on Google at some point. and b) Reach Mauricio Dinarte\u0026rsquo;s blog: understanddrupal.com. For me, this website has been a very important reference (almost mandatory) to understand how to perform Drupal Migrations. In fact, it was one of the recommended authors that I considered fundamental to learn how to execute Drupal Migrations, when more than a year ago I started writing about this deep topic here in The Russian Lullaby, linking to his website and the platform of Agaric.coop, with shared related content, too. So the author and his work is not alien to me, but what happens is that today I want to talk about a novelty discovered recently: the compilation of all the great work around this topic in his book about migration processes in Drupal: 31 days of Drupal Migrations.\n2- The Book It is difficult for me to evaluate this book as it deserves. So maybe the first thing I want to say is that this is the first time I have found such an integrated and compact body of knowledge and experience. This is very important, because in many cases Drupal documentation is not very extensive or not sufficiently updated. When the topic is complex or advanced, this becomes a real problem: you need to consult outdated blogs, review contributed modules, read code, and check deprecated documentation just to build, step by step, an idea of how to do this or that.\nMaybe because of this is why initiatives like this from Mauricio Dinarte, @dinarcon from the initiative Understand Drupal are so important and generate a very high value (only surmountable if the same information becomes part of the official Drupal.org documentation, of course).\nAs I said at the beginning of this section, it\u0026rsquo;s difficult for me to describe how much this resource has helped me, but I would like write down some special points that you can learn from this book, some key points of interest, something like:\nKey Points:\n   Perhaps the first fundamental learning here: to know how a migration process works, what are the workflows and its possibilities.\n  Learn about how to migrate data in a lot of entities and resources: files, images, paragraphs, fields and subfields\u0026hellip;All full of a) theory and concepts and b) examples, examples, and more examples.\n  Know the way to perform migrations from diverse sources and origins: XML files, CSV, JSON\u0026hellip; executing processing ad-hoc for the values.\n  Get the most complete list of the most valuable contributed modules related to Migrations: Media Handler, Media Migration, Geofield, Migrate Devel\u0026hellip;core modules, contrib modules, modules that includes plugins from Migrations\u0026hellip;the set is long and pretty interesting. This book contains the most comprehensive and centralized list of all.\n  3- Recommendations Actually, I guess I could say - in a nutshell - that any work team implementing Drupal-based projects should have a copy of this book available. And I wouldn\u0026rsquo;t be exaggerating, I swear. I think this huge, extensive and thorough work compiled by Mauricio Dinarte has too much importance and it\u0026rsquo;s quite interesting too. This cannot go unnoticed.\nOn the other hand, because \u0026ldquo;Migrations\u0026rdquo; is a topic that encompasses and relates to other Drupal APIs, it can also give people on a technical team a lot of knowledge about Drupal internals: Plugins, Services, Entities, Drush, Debugging\u0026hellip;these are cross-cutting topics in this book, and they are also very important issues. Maybe you want to learn more about that.\nFor everyone who has to go through ETL processes in Drupal, this is a must-have resource: you can\u0026rsquo;t get through the maze without using a good map of the territory and this chart is the best compilation yet of all the key issues when defining a migration process: Do you know under which criteria to decide how to define your migration? code or configuration? advantages and disadvantages?\u0026hellip; These are just the initial questions. Along the way there are many more, and this is the best guide to deal with it. The fact that this e-book is also available for only $10 makes it even more obvious: there are no excuses. It\u0026rsquo;s a must. And then, if you want you can be supporter of the Understand Drupal initiative, sponsoring the tutorials production: understanddrupal.com/supporters. Think about it.\n4- Book Information    Field Description     Title 31 Days of Drupal Migrations.   Author Mauricio Dinarte.   Publisher Leanpub.   Date October, 2020.   Pages 193   Overview Complete guide to implement migrations and ETL processes in Drupal.   Keywords Drupal, Migration, Migrate API, Plugins, Source, Process, Destination.   Price 10$   Links Gumroad,    5- Fast Review    Question // Answer     1- Is this book progressive, Iterative and Incremental?   Yes, it is. It\u0026rsquo;s constructed in a very didactic and useful way.   2- Does it offer specific solutions to particular problems or concrete issues?   Yes, it offers solutions. The book contains many (many) practical examples and errors.   3- Does it explain well the original problems or needs it aims to solve?   Yes, the book is built from the needs that normally originate migration tasks.   4- Is this book rich in examples?   Yes, for each situation, case, resource, it offers a real and practical example, downloadable from a repository. .   5- Is this book written in plain English, suitable for non-English speakers?   Yes, this book is an easy read for non-English speakers, very pleasant.   6- Is it up to date?   Yes, seems to be aligned with the last big changes related to the Migrate API of Drupal, November 2020.    6- Ratings 7- :wq! Recommended song: Sonny Rollins - Alfie\u0026rsquo;s Theme   ","permalink":"https://www.therussianlullaby.dev/blog/books-31-days-of-drupal-migrations/","tags":["Books","Drupal Development","Backend","PHP","Drupal Migrations"],"title":"Books/ 31 Days of Drupal Migrations"},{"categories":["Development"],"contents":"Hello there! In this new post I want to focus on a very interesting topic of Drupal, which pertains to its extension capabilities through its Plugins system. It is not a very extensive topic, but it is also true that there is not much documentation about it. This is a very common case when you\u0026rsquo;re building some kinds of Drupal Blocks to render specific data and you need that its visualization and its behaviour responds to special conditions.\n Picture from Unsplash, user Benjamin Child, @bchild311\n Table of Contents\n1- Introduction 2- What are Condition Plugins 3- Existing Condition Plugins in your Drupal installation 4- Available Condition Plugins in Drupal Contrib Modules 5- Writing your own Condition Plugin 6- :wq!\n 1- Introduction Every Drupal Site Builder works with blocks, blocks are a basic and essential piece of functionality in Drupal and a very important building element in Drupal-based projects. And one of the most important phases of working with blocks in Drupal is managing the rules for the visibility of this important resource: We need determine when show it and when hide it.\nWe have all had at one time or another, to work with visibility conditions for a Block. It happens when we\u0026rsquo;re using the config page of a Block in Drupal, seeing something like this:\nWell, for this post I was thinking on writing about how to expand these visibility conditions for our own custom needs in a Drupal Project. We\u0026rsquo;re going to talk about the Condition Plugins in Drupal.\n2- What are Condition Plugins? Although there is not a lot of information on this subject, nor does it form part of the documented APIs of Drupal, We can assemble interpretative pieces about how this small extensible sub-system based on Drupal Plugins works. Basically, we can say that Condition Plugins are an extensible way to generate new visibility conditions for Blocks in Drupal, based on the format of Plugins that you can implement following the Drupal rules for Plugins.\nThe Condition Plugins are context-aware (many of them requires explicit context in its annotations blocks), but let\u0026rsquo;s see some information located within the Drupal documentation:\nFrom the Condition Plugin System, as was initially described in 2013:\n \u0026ldquo;To implement a condition in a module create a class in {module}/src/Plugin/Condition/{ConditionName}.php and extend ConditionPluginBase (which implements ConditionInterface). The class must also declare a plugin annotation in its docblock comment.\u0026quot;\n And about the Contexts, from the Plugin Contexts Definition:\n \u0026ldquo;Sometimes plugins require another object to perform their primary operation. This is known as plugin context. Using a practical example, almost all condition plugins require a context. Let\u0026rsquo;s look at the NodeType condition\u0026rsquo;s plugin definition [\u0026hellip;] The context_definitions key stores an array of named context definitions the condition requires to perform its \u0026ldquo;evaluate()\u0026rdquo; method.\u0026quot;\n Actually, the Condition Plugin it\u0026rsquo;s not a complicated concept, but it\u0026rsquo;s true that you need to know quite well some basic previous steps to reach here the Satori. These are key concepts for Drupal Development and maybe you\u0026rsquo;ll need understand in deep the next topics:\n Creating Custom Modules in Drupal. The Plugin API in Drupal. Annotations Based Plugins in Drupal. Guide: How to integrate JavaScript in Drupal 8-9  3- Existing Condition Plugins in your Drupal Installation You can discover some existing Condition Plugins available in your Drupal installation from different core modules:\nCurrent Condition Plugins in Core:\n NodeType Condition Class: namespace Drupal\\node\\Plugin\\Condition, in core/modules/node RequestPath Condition Class: namespace Drupal\\system\\Plugin\\Condition, in core/modules/system UserRole Condition Class: namespace Drupal\\user\\Plugin\\Condition, in core/modules/user  These previous Plugins are the three basic items availables by default in a Block Visibility Configuration, I mean:\nBut there are a few more in other locations (in addition to some used in test classes):\n CurrentTheme Condition Class: namespace Drupal\\system\\Plugin\\Condition, in core/modules/system Language Condition Class: namespace Drupal\\language\\Plugin\\Condition, in core/modules/language  And the central Interface for Conditions:\n ConditionInterface: namespace Drupal\\Core\\Condition, in core/lib/Drupal/Core/Condition  4- Available Condition Plugins in Drupal Contrib Modules As in Drupal Core you can find Condition Plugins in Contrib modules too. For instance, there are some modules developed by Cambrico with many resources within:\n  Drupal Module Condition Plugins\n  Drupal Module Condition Plugins Commerce\n  And one of the biggest contrib modules of Drupal -Webform- also provides its own Plugin for Conditions:\n Webform Condition  5- Building your own Condition Plugin Ok, now let\u0026rsquo;s go to build something useful. We already know conceptually what they are, what they do and where cand find them. So is the time to implement Condition Plugins!.\nOur Goals For this case, I have come up with a very simple and straightforward idea: We want to show a block only for nodes of type \u0026ldquo;Article\u0026rdquo; when they have checked a field.\nIn order to prepare this, I made the quickest way:\n First I added a new field for the content type \u0026ldquo;Article\u0026rdquo;, the new \u0026ldquo;field_selected_article_check\u0026rdquo;. Then I created a new custom block \u0026ldquo;My Block\u0026rdquo; in the most easy way, just clicking in /block/add. Finally I implemented scaffolding for a new custom module, called \u0026ldquo;visibility_conditions\u0026rdquo;  We\u0026rsquo;re going to make a new folder in path: /modules/custom/visibility_conditions/src/Plugin/Condition/\nand creating the new file SelectedArticle.php This will be the new Plugin class and will have all the basic resources. Let\u0026rsquo;s see.\nAnnotations In order to create a new vertical tab for the block configuration page, we hace to register our new Plugin, and in Drupal the Plugins uses annotations to register themselves and provide all the relevant information for indexing. For instance:\n/** * Provides a condition for articles marked as selected. * * This condition evaluates to TRUE when is in a node context, and the node is * Article content type and the article was marked as selected article in a field. * * @Condition( * id = \u0026quot;selected_article\u0026quot;, * label = @Translation(\u0026quot;Selected Article\u0026quot;), * context_definitions = { * \u0026quot;node\u0026quot; = @ContextDefinition(\u0026quot;entity:node\u0026quot;, label = @Translation(\u0026quot;node\u0026quot;)) * } * ) * */ What about context? Well It\u0026rsquo;s a way to know where is running our resource. Some Drupal\u0026rsquo;s Plugins require context information to operate. In this case, we need to know if our Block is inside a node corpus, or not.\nFor the next lines, we have to extend the ConditionPluginBase Class and implement the ContainerFactoryPluginInterface Class:\n class SelectedArticle extends ConditionPluginBase implements ContainerFactoryPluginInterface { And this is the most relevant information from the top of our new class (as well as the \u0026lsquo;\u0026lt;?php\u0026rsquo; operture tag). Let\u0026rsquo;s continue.\nConfiguration Ok, our next step is define the configure by our new Plugin: what values are important and how we go to get these values. For the first need, we build a basic method defaultConfiguration() from the mother Class. This will set the initial value of the new field in the tab:\n /** * {@inheritdoc} */ public function defaultConfiguration() { // This default value will mark the block as hidden. return ['show' =\u0026gt; 0] + parent::defaultConfiguration(); } /** * {@inheritdoc} */ public function buildConfigurationForm(array $form, FormStateInterface $form_state) { // Build a checkbox to expose the new condition. $form['show'] = [ '#title' =\u0026gt; $this-\u0026gt;t('Display only in Selected Articles'), '#type' =\u0026gt; 'checkbox', // Is using the previous config value as the default. '#default_value' =\u0026gt; $this-\u0026gt;configuration['show'], '#description' =\u0026gt; $this-\u0026gt;t('If this box is checked, this block will only be shown for Selected Articles.'), ]; return parent::buildConfigurationForm($form, $form_state); } And then we call to the submit function:\n /** * {@inheritdoc} */ public function submitConfigurationForm(array \u0026amp;$form, FormStateInterface $form_state) { // Save the selected value to configuration. $this-\u0026gt;configuration['show'] = $form_state-\u0026gt;getValue('show'); parent::submitConfigurationForm($form, $form_state); } After that, we\u0026rsquo;ve built an extension for the Configuration Form of the visibility general block, adding a new item called show which contains the new element, a checkbox for select our new condition (or not).\nEvaluate Now we need a way to check whether our new condition applies, so we use a function inherited from the parent class to evaluate the condition and return TRUE or FALSE. It comes from the ConditionBase class, which implements ConditionInterface:\n /** * {@inheritdoc} */ public function evaluate() { // First ensure that doesn't disable other blocks aren't using it. if (empty($this-\u0026gt;configuration['show']) \u0026amp;\u0026amp; !$this-\u0026gt;isNegated()) { return TRUE; } // Gets the context value. $node = $this-\u0026gt;getContextValue('node'); // Then review if the Article node has the Selected Article field set. if (($node-\u0026gt;getType() == \u0026quot;article\u0026quot;) \u0026amp;\u0026amp; ($node-\u0026gt;hasField('field_selected_article_check')) \u0026amp;\u0026amp; ($node-\u0026gt;field_selected_article_check-\u0026gt;value)) { return TRUE; } // Finally if not exist marked value in Selected Article field. return FALSE; } As you can see in the block above, we\u0026rsquo;re using a first part to ensure that if none of the above fields are checked, TRUE is returned and the block is displayed. This will ensure that by default if we have not activated the visibility of the block, we have it visible in general terms until we limit its visibility.\nSummary Once again, we have a function available from the parent ConditionBase class and required by ConditionInterface. It is a method for exposing descriptions of how the conditions work. These descriptions will not be available from the vertical tabs of the block configuration page, only from code.\n /** * {@inheritdoc} */ public function summary() { // We have to check three options: // 1- Condition enabled. // 2- Condition enabled and negated. // 3- Condition not enabled. if ($this-\u0026gt;configuration['show']) { // Check if the 'negate condition' checkbox was enabled. if ($this-\u0026gt;isNegated()) { // The condition is enabled and negated. return $this-\u0026gt;t('The block will be shown in all Articles except the Selected.'); } else { // The condition is only enabled. return $this-\u0026gt;t('The block will be shown only in Selected Articles.'); } } // The condition is not enabled. return $this-\u0026gt;t('The block will be shown in all Articles.'); } Some Futher Details In the previous section, we took care of preparing a Summary, but this is only available at code level, it\u0026rsquo;s not visible in GUI. Could we improve the usability of our new element by adding this summary? Yes, it\u0026rsquo;s possible and for this we can rely on a jQuery function in Drupal (sorry).\nI introduce you the function \u0026ldquo;drupalsetSummary\u0026rdquo;, I discovered it consulting problems in StackExchange and then I identified it in real cases like the JavaScript added for Nodes in the Drupal core. I haven\u0026rsquo;t found much information about it, so I can\u0026rsquo;t explain much more than this: \u0026ldquo;it serves to place summaries\u0026rdquo; ¯_(ツ)_/¯ . Well, ok.\nThe important thing is that I was playing with this function and it seems to work well for inserting elements. The pre-conditions are that JavaScript must be added to our custom module and that we must use the Drupal Behaviors format. Please review this Drupal - JavaScript integration guide: Drupal Behaviors to get more info about the Drupal Behaviors format and how to implement it, and here from this GitHub gist you can play with some examples of Drupal Behaviors.\nFile: visibility_conditions.libraries.yml:\nblock_special_articles: js: js/selected_articles_conditions.js: {} dependencies: - block/drupal.block File: selected_articles_conditions.js The Drupal Behavior:\n /** * Provide the summary information for the block settings vertical tabs. * */ Drupal.behaviors.blockSettingsSummarySelectedArticles = { attach: function () { // Check if the function drupalSetSummary is available. if (jQuery.fn.drupalSetSummary !== undefined) { // Add the summary on tab. $('[data-drupal-selector=\u0026quot;edit-visibility-selected-article\u0026quot;]').drupalSetSummary(checkSummary); } } }; And the function for setting the strings of summary:\nfunction checkSummary(context) { // Check if the condition has been selected. var selectedCondition = $(context).find('[data-drupal-selector=\u0026quot;edit-visibility-selected-article-show\u0026quot;]:checked').length; // Check if the negate condition has been selected. var selectedNegate = $(context).find('[data-drupal-selector=\u0026quot;edit-visibility-selected-article-negate\u0026quot;]:checked').length; // Reviews scenarios. if (selectedCondition) { if (selectedNegate) { // Condition and Negation were selected. return Drupal.t(\u0026quot;The block will be shown in all Articles except the Selected.\u0026quot;); } // Only the condition was selected. return Drupal.t(\u0026quot;The block will be shown only in Selected Articles.\u0026quot;); } // The condition has not been enabled and is not negated. return Drupal.t('The block will be shown in all Articles'); } Now you need enable the new custom module by drush en -y visibility_conditions from your command line. Clear cache by doing drush cr and\u0026hellip;That\u0026rsquo;s all! interesting? See the new Condition Plugin in action:\nAnd the JavaScript rendering the info about summary:\nIt\u0026rsquo;s true that it requires basic knowledge of other Drupal elements (Annotations, Plugins, JavaScript Libraries, Drupal Behaviors\u0026hellip;) but in itself, the result is quite interesting. Now we can build our own visibility conditions Plugins!\nI have uploaded this test module to my custom modules repository for testing, in Gitlab. You can download it from here. And remember, don\u0026rsquo;t use these modules in production environments!\n6- :wq! Recommended song: Fabrizio de Andrè - La ballata dell\u0026rsquo;amore cieco   ","permalink":"https://www.therussianlullaby.dev/blog/condition-plugins-for-visibility-in-drupal/","tags":["Drupal Behaviors","Drupal Development","Backend","PHP"],"title":"Condition Plugins for Visibility in Drupal 8-9"},{"categories":["Development"],"contents":"Imagine that you have to integrate JavaScript code into your Drupal project\u0026hellip; Where do you start? How do you do it? You\u0026rsquo;re looking for information but you don\u0026rsquo;t find anything \u0026ldquo;holistic\u0026rdquo;, something that goes from 0 to 100 and that puts in context how the relationships between Drupal and JavaScript are structured. Well, this article was made for you (Or for other people in your team that you want to introduce to this topic).\nIn this guide you will learn basic concepts of JavaScript, the terminology used in Drupal, functions, methods and common mechanics to enrich your projects by make them run with executable code on the client side. And all through a combination of theory and practice. It includes some exercises that I have integrated.\n Picture from Unsplash, user Magnus Engø.\n Index of sections\n1-Introduction 2- JavaScript and Drupal: basic concepts 3- How to include JavaScript code in Drupal\n  3.1- Setting up the scenario: creating a custom module 3.2- The Library concept \u0026raquo;* 3.2.1- Secuence for creating libraries \u0026raquo; * 3.2.2- Loading libraries in head \u0026raquo; * 3.2.3- Libraries as external resources \u0026raquo; * 3.2.4- Libraries and dependencies 3.3- The JavaScript file 3.4- Adding JavaScript Libraries \u0026raquo; * 3.4.1- Using the attached property in Render Arrays \u0026raquo; * 3.4.2- Libraries in a TWIG template \u0026raquo; * 3.4.3- Global libraries for a Theme \u0026raquo; * 3.4.4- Adding libraries from hooks   4- Just a little bit more of JavaScript in Drupal\n  4.1- Structure and Guidelines for IIFE 4.2- Passing parameters in IIFE 4.3- Passing values from PHP to JavaScript using drupalSettings 4.4- Changes in rendered HTML \u0026raquo; * 4.4.1- Counting visits using web storage   5- Drupal and the old jQuery\n  5.1- Fast Review of the jQuery keys 5.2- Availability of jQuery in our Drupal version 5.3- Using a different version of jQuery   6- Drupal Behaviors\n  6.1- Anatomy of a Behavior 6.2- The global object: Drupal 6.3- Behaviors in Drupal   7- JavaScript without JavaScript: #ajax, #states\n  7.1- Brief Introduction to AJAX in Drupal 7.2- Rendering elements with #states propery   8- Troubleshooting: Problems and Solutions\n  8.1- Slow execution due to wrong use of context 8.2- Loading JavaScript out of context 8.3- Error: Illegal choice in dynamic select   9- Links and reading resources\n  9.1- JavaScript fundamentals 9.2- Functions in JavaScript and the IIFE format 9.3- JavaScript and Drupal 9.4- jQuery 9.5- Snippets 9.6- Others   10- :wq!\n Index of Exercises\nExercise 1: Creating a basic custom module Exercise 2: Defining our new custom library Exercise 3: Defining our initial JavaScript file Exercise 4: Adding libraries to our Drupal custom module Exercise 5: Passing values to the IIFE format Exercise 6: Transfering values trough drupalSettings Exercise 7: Custom Visit Counter with JavaScript Exercise 8: Changes based on jQuery Exercise 9: Dialog Window from the global object Drupal Exercise 10: Image Board from Unsplash using Drupal Behaviors\n 1- Introduction Some time ago (around December 2019, but it seems a century has passed ) I started writing what I thought would be a simple guide to integration between JavaScript and Drupal. A couple of months later, in February 2020, I had a tutorial of more than eleven thousand words written in Castillian (Spanish from Spain) that I published in my Medium profile.\nWhat was initially going to be brief has become a kind of reference guide on JavaScript and Drupal and (as far as I know) is now part of the training resources shared in many companies in Spain and other Latin American countries. Here you can reach the original publication in Medium, the so called: JavaScript \u0026amp; Drupal 101 TUTORIAL HANDBOOK TOTAL MAX POWER 2000 (I can swear I had a lot of fun thinking about the title).\nWell, the fact is that since the publication, I received three basic types of feedback:\n \u0026ldquo;Hey, this is wrong, you have to check it\u0026rdquo; \u0026ldquo;We have people in the company from other countries, do you have it translated into English?\u0026rdquo; \u0026ldquo;Thank you for not putting it behind the Medium payment wall\u0026rdquo;  So although my first intention was to move all this content to an open book format like git-book or something like that, I\u0026rsquo;ve actually grouped the first two together and I\u0026rsquo;m going to publish a review of the original post translated into English. As always, I hope it can be useful to someone.\nIn a complementary way, you can download all the code from the exercises grouped as a single Drupal custom module, available here: gitlab.com/davidjguru/javascript_custom_module. This works in Drupal 8 and Drupal 9.\nDISCLAIMER: This guide is actually a manual for the integration of JavaScript code in Drupal-based projects, but only in the context of implementing Drupal modules. This is basically a backend issue. This guide does not contain information related to JavaScript frameworks (React, Angular, Vue) or about the use of Drupal headless as decoupled. Neither does it deal with Drupal Theming issues and its approach to them is only tangential. This tutorial is only for people related to the Drupal backend.\nThere we go!\n2- JavaScript and Drupal: basic concepts If this is your first approach to the intersection between Drupal and JavaScript and it may even be your first approach to Drupal and its world, it\u0026rsquo;s convenient that you review this section beforehand, in which we are going to share some terms and names that we will use throughout the tutorial.\nBy this way you will know what we are talking about at any time in the manual and you will be able to follow the cases, examples and exercises more easily.\n  Drupal: Our technological platform of reference in this context. Something halfway between the framework and the CMS, free software downloadable and installable from here: https://www.drupal.org. In this tutorial we\u0026rsquo;ll travel over the shoulder of a Drupal, so it is good to know it.\n  Render Array: It\u0026rsquo;s a key piece of Drupal to \u0026ldquo;paint\u0026rdquo; on screen. They are multidimensional arrays that must meet certain rules using different properties to model the elements to be rendered. The elements we usually draw are described here: drupal.org/api/drupal/elements/9.2.x. Most of the connections between Drupal and JavaScript will be done from Drupal\u0026rsquo;s render arrays, so is highly recommended to know them and learn its declarative format.\n  JavaScript: A programming language very diversified so much as to be the basis of many frameworks, libraries and tools in fashion. Today it\u0026rsquo;s executable both in client and server. In this context we will use the so called \u0026ldquo;Vanilla JavaScript\u0026rdquo;, that is, the own handcrafted code outside JS platforms. See a guide from Mozilla: mozilla.org/JavaScript/Guide. In this tutorial, although it is not an advanced JavaScript manual, we will use this language in several sections, so is great that you know it a little bit.\n  Immediately-invoked Function Expressions(IIFE): Also called \u0026ldquo;self-executing\u0026rdquo; function, it\u0026rsquo;s a specific format to declare JavaScript functions so they are executed as they are declared, as soon as they are defined. See: flaviocopes.com/javascript-iife to understand better this important concept. In this article we tried to integrate JavaScript into Drupal through this format, so it would be optimal if you at least understand the concept.\n  AJAX: This stands for Asynchronous JavaScript + XML, a combination of technologies for use partial requests (lighter than complete requests) from the client to the server, which results in speed and performance improvements. See more: developer.mozilla.org/Guide/AJAX. Although it is a complex and extensive topic, we will focused in the possibilities of implementing AJAX in Drupal.\n  DOM: The Document Object Model is the tree structure that represents all the HTML code used in the representation of the web we are visiting. See: developer.mozilla.org/Glossary/DOM. In this guide we are going to make modifications and operations on HTML elements, so we will learn how to make changes on the DOM from Drupal.\n  jQuery: It\u0026rsquo;s a mythical library based on JavaScript to facilitate (theoretically) manipulations of the DOM. In Drupal it (still, by now) maintains a very extensive presence, so we better get along with it. See: developer.mozilla.org/Glossary/jQuery. We\u0026rsquo;re going to execute jQuery code in the Drupal context.\n  3- How to include JavaScript code in Drupal We will practice with the inclusion of JavaScript code in our project. To do this, we will create a new custom module and iterate on it providing you with JavaScript based functionality while we discuss the most important concepts in the following sections. To doing this, I recommend quickly creating a containerised test environment, using DDEV to deploy a Drupal installation on the fly. If you don\u0026rsquo;t know DDEV, you can follow my own guide published in Digital Ocean: How To Develop a Drupal 9 Website on Your Local Machine Using Docker and DDEV.\nYou can also deploy a lightweight version of a Drupal installation just using your PHP local config, with a light server. Follow the steps in the next snippet:\n Drupal 8 || 9 : Ultra-lightweight deploy of Drupal setup (without Apache or MySQL)  3.1- Setting up the scenario: creating a custom module To begin with, let\u0026rsquo;s define the new custom module we will work with. I don\u0026rsquo;t know what context you have with respect to Drupal, so I\u0026rsquo;ll write down here a sequence of links that you can update with. You will need a Drupal deploy, maybe XAMP+ environment with web server, database and a Drupal deployed and ready to use, or if you\u0026rsquo;re using DDEV (as I recommended in the previous section).\nExplaining how to create a custom module for Drupal is beyond the scope of this guide, but here are some links to read:\n Drupal.org guide: Creating Custom Module  Snippets\n Drupal 9 in six steps using DDEV: Quick Deploy Drupal 8 || 9: Deploying a new Drupal Site with Composer / Drush on the fly Drupal 8 || 9: Creating modules and forms using Drupal Console  Exercise 1: Creating a basic custom module for testing In case you already have a Drupal site available for testing (including use of Drupal Console), just type this from the console while being inside your project and Drupal Console will take care of creating the new module:\n// Using Drupal Console with params. drupal generate:module \\ --module=\u0026quot;Custom Module for JavaScript\u0026quot; \\ --machine-name=\u0026quot;javascript_custom_module\u0026quot; \\ --module-path=\u0026quot;modules/custom\u0026quot; \\ --description=\u0026quot;This is a custom generated module for JavaScript.\u0026quot; \\ --package=\u0026quot;Custom\u0026quot; \\ --module-file \\ --no-interaction If Drupal Console is not your option, you can use Drush, launching the command:\n$ drush generate $ ddev drush generate And you\u0026rsquo;ll get a list of options, including:\n module: module-configuration-entity Generates configuration entity module module-content-entity (content-entity) Generates content entity module module-file Generates a module file module-standard (module) Generates standard Drupal 8 module And ask for a custom module creation with params, avoiding all parameters setting through dialogue:\n$ drush gen module-standard --directory modules/custom --answers '{\u0026quot;name\u0026quot;: \u0026quot;Custom Module for JavaScript\u0026quot;, \u0026quot;machine_name\u0026quot;: \u0026quot;javascript_custom_module\u0026quot;, \u0026quot;description\u0026quot;: \u0026quot;Custom Generated Module for JavaScript.\u0026quot;, \u0026quot;package\u0026quot;: \u0026quot;Custom\u0026quot;, \u0026quot;dependencies\u0026quot;: \u0026quot;\u0026quot;, \u0026quot;install_file\u0026quot;: \u0026quot;no\u0026quot;, \u0026quot;libraries\u0026quot;: \u0026quot;no\u0026quot;, \u0026quot;permissions\u0026quot;: \u0026quot;no\u0026quot;, \u0026quot;event_subscriber\u0026quot;: \u0026quot;no\u0026quot;, \u0026quot;block_plugin\u0026quot;: \u0026quot;no\u0026quot;, \u0026quot;controller\u0026quot;: \u0026quot;no\u0026quot;, \u0026quot;settings_form\u0026quot;: \u0026quot;no\u0026quot;}' See an example here: Drupal 8 || 9 : Creating custom module using Drush generate.\nYou can also download this basic custom module created for examples from my gitlab repository: gitlab.com/davidjguru/basic_custom_module, or doing git clone from the whole repository for custom modules: gitlab.com/davidjguru/drupal-custom-modules-examples.\nThis module is quite simple and basic, only for first setps in Drupal: when enabled, only creates a new path /basic/custom with a Controller that gives you a response creating a render array in Drupal, with a very simple markup message for HTML. With this, we can start to test.\nWe will now generate some content automatically for our exercises / test scenario. We can rename the custom module if we want, to particularize it a bit more (I\u0026rsquo;ll use the naming javascript_custom_module to avoid confusion with other test modules. We will install, activate and generate a random comments set within our platform. To do this we\u0026rsquo;ll use the Drupal Devel Module and its Devel Generate sub-module to create test content, adding new commands and sub-commands to Drush. We\u0026rsquo;ll use Composer and Drush from inside the console project folder, just by typing:\n$ composer require drupal/devel $ drush en devel devel_generate $ drush genc 10 5 --types=article With these instructions above we asked to devel-generate to create ten items, using the type nodes (default in Drupal) with a comments set in each node, between 0 and 5 per node. We now have ten initial nodes to build our initial exercise scenario:\nNext, we will reorder what this example Controller originally returned. Until now it was simply a text message, but now we are going to add a table with comments associated with the current user. To do this we are going to perform a database query using the database service, extract the returned values and process them by launching them into the table rendering system. For the query filtered by the current user data through the current_user service .\nLet\u0026rsquo;s see, now the controller class would look like this:\n What once enabled the test module (using Drush or Drupal Console -if it works in your Drupal installation-):\n$ drush en -y javascript_custom_module $ drupal moi javascript_custom_module This will generate the /javascript/custom path through the Controller and it will render on screen the following table:\nWith this step, we have already prepared the initial scenario and can move on to perform exercises directly with JavaScript.\nNext!\n3.2- The \u0026ldquo;library\u0026rdquo; concept Working with both CSS and JS from Drupal 8 onwards has become standardised. In previous versions of Drupal you had to use specific functions to add CSS or JS resources. As I explained in this snippet: Drupal 8 || 9 : Altering HTML in headers from hooks, you had to use things like drupal_add_html_head() to add new HTML tags, drupal_add_js() to incorporate JavaScript or the drupal_add_css() function to add more style sheets.\n3.2.1- Secuence for creating libraries From Drupal 8, the sequence of inserting libraries has been standardised, and consists of fulfilling these three steps:\n Create the CSS/JS files. Define a library that includes these files. Add this library to a typical Drupal render array.  But in this case, we are going to reverse steps 1 and 2: first we will see how to create the library and then we will talk about the JavaScript file itself, which could be a little more complex.\nExercise 2: Defining our new custom library Let\u0026rsquo;s see\u0026hellip;in our custom module, we\u0026rsquo;ll include a new file module_name.libraries.yml to describe the new dependencies, so in our case study, we\u0026rsquo;ll create a new file called javascript_custom_module.libraries.yml filled with the next lines:\n// Case 1: Basic library file with only JavaScript dependencies. module_name.library_name: js: js/hello_world.js: {} // Example custom_hello_world: js: js/hello_world.js: {} All the libraries will be declared, as a rule of style, in the same .libraries.yml file, where we will describe all the libraries we need in our project, grouped by function or use.\nHere you can see several examples of definition of libraries for Drupal with some example models:\n As we can see in the examples listed in the previous gist, there are different ways to declare libraries and even to add them externally. About the declaration of libraries, we can add a couple of curiosities that are nice to know:\n3.2.2- Loading libraries in head By default, all libraries will tend to be loaded into the footer: To avoid operations over elements in DOM (Document Object Model) that have not yet been loaded, JS files will be included at the end of the DOM. If for some reason you need to load it at the beginning, then you can declare it explicitly using the pair parameter/value \u0026ldquo;header: true\u0026rdquo;:\njs_library_for_header: header: true js: header.js: {} js_library_for_footer: js: footer.js: {} 3.2.3- Libraries as external resources We are looking at examples of creating our own custom libraries, but it\u0026rsquo;s also possible to declare in the .libraries.yml file of our custom module the use of an external library that is available via CDN or by an external repository.\nIt is possible to request to Drupal the use of an external library to incorporate it to our project, as we can see in the example of the use of backbone.js in the Drupal core, created by third parties, incorporated to Drupal and declared coherently with their external data:\nBy the way, in the same file core.libraries.yml you\u0026rsquo;ll can see all the JavaScript resources declared from the core of Drupal. Some of these resources will be used here in this guide. ;-)\nIn this former example about backbone.js in Core, we\u0026rsquo;re seeing that finally, the library is used from a local environtment, right? so\u0026hellip;It is possible loading a library directly from remote? we\u0026rsquo;ll see the official documentation from Drupal saying something like this:\n “You might want to use JavaScript that is externally on a CDN (Content Delivery Network) to improve page loading speed. This can be done by declaring the library to be “external”. It is also a good idea to include some information about the external library in the definition.”\n  Source: Drupal.org/docs Adding js to a Drupal Module  So we can do something like this:\nangular.angularjs: remote: https://github.com/angular/angular.js version: 1.4.4 license: name: MIT url: https://github.com/angular/angular.js/blob/master/LICENSE gpl-compatible: true js: https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.min.js: { type: external, minified: true } Quite interesting, right?\n3.2.4- Libraries and dependencies It is possible that within our JavaScript code, in your own .js file, we may need to use another third-party library for our functionality. Well, in that case, we can declare libraries with dependencies following a basic vendor/resource or vendor/library scheme.\nLet\u0026rsquo;s see an example in which we intend to use a hide/show effect. As such animations are available in the jQuery library and it\u0026rsquo;s integrated in Drupal (we will see it later), then instead of creating those functions we\u0026rsquo;ll declare the dependency and we will be able to use them:\njs_library_hide_show: js: js/my_custom_javascript_library.js: {} dependencies: - core/jquery In addition, there is a set of options that you can use as attributes to customize the use of your new CSS / JavaScript libraries. See: Drupal org Docs: Libraries options and details.\n3.3- The JavaScript file The next step will be to define that JavaScript file that we have declared as a resource within the new previous library.\nExercise 3: Defining our initial JavaScript file For that, we\u0026rsquo;ll create a /js folder and will put inside our new file hello-world.js which contains our new library with a little action, just say hello by Console:\n(function () { 'use strict'; // Put here your custom JavaScript code. console.log (\u0026quot;Hello World\u0026quot;); })(); So the internal structure of our custom module for testing should look like this:\n/javascript_custom_module /js javascript_file_name.js /src /Controller YourCustomExampleController.php javascript_custom_module.info.yml javascript_custom_module.routing.yml javascript_custom_module.libraries.yml 3.4- Adding JavaScript libraries Now our goal is linking the new library with its JavaScript .js file associated with the context in which it should work, right? Well, for that we are going to make a base case and then we are going to add more probable cases, given that in Drupal it is possible to attach JavaScript libraries in various ways, depending on how we need to use them in our code.\nBut let\u0026rsquo;s see first the base case for our case: #attached.\n3.4.1- Using the #attached property in Render Arrays On one hand, we have the eternal Drupal Render Arrays, that is, the arrays loaded with properties, values, parameters and others that we use to send to the Drupal rendering system so it transforms everything and ends up painting HTML renderable in a browser.\nOn the other hand, we have a property called \u0026ldquo;#attached\u0026rdquo; that offers us a set of already defined sub-properties that allow us to attach resources of different nature to any render array we are using (a controller response, a form build, etc):\n Library -\u0026gt; $render_array['#attached\u0026rsquo;][\u0026lsquo;library\u0026rsquo;] drupalSettings (from PHP to JavaScript) -\u0026gt; $render_array['#attached\u0026rsquo;][\u0026lsquo;drupalSettings\u0026rsquo;] Http_Header -\u0026gt; $render_array['#attached\u0026rsquo;][\u0026lsquo;http_header\u0026rsquo;] HTML Link in Head -\u0026gt; $render_array['#attached\u0026rsquo;][\u0026lsquo;html_head_link\u0026rsquo;] HTML Head -\u0026gt; $render_array['#attached\u0026rsquo;][\u0026lsquo;html_head\u0026rsquo;] Feed -\u0026gt; $render_array['#attached\u0026rsquo;][\u0026lsquo;feed\u0026rsquo;] Placeholders -\u0026gt; $render_array['#attached\u0026rsquo;][\u0026lsquo;placeholders\u0026rsquo;] HTML Response Placeholders -\u0026gt; $render_array['#attached\u0026rsquo;][\u0026lsquo;html_response_attachment_placeholders\u0026rsquo;]  We will come back to some of these cases in following sections, But for more info about the processing of attached resources, You can visit the official documentation in Drupal.org: public function HtmlResponseAttachmentsProcessor.\nSee some examples at:\n davidjguru.github.io/the-magic-of-attached. dev.to/davidjguru/erasing-traces-of-generator-in-drupal-projects  Exercise 4: Adding libraries to our Drupal custom module By now, we just need to go to the PHP class file (The Controller) and modify the render array that is returned at the end, including the #attached property with our new library:\n// Path: javascript_custom_module/src/Controller/ // File: CommentsListController.php // Function: gettingList() // Before (line 42): $final_array['welcome_message'] = [ '#type' =\u0026gt; 'item', '#markup' =\u0026gt; $this-\u0026gt;t('Hello World, I am just a text.'), ]; // Now (line 42): $final_array['welcome_message'] = [ '#type' =\u0026gt; 'item', '#markup' =\u0026gt; $this-\u0026gt;t('Hello World, I am just a text.'), '#attached' =\u0026gt; [ 'library' =\u0026gt; [ 'javascript_custom_module/js_hello_world_console', ], ], ]; // Form : $attachments['#attached']['library'][] = 'module/library'; Just after changed it, We will reinstall our custom module, clearing cache:\n$ drush pmu javascript_custom_module $ drush en -y javascript_custom_module $ drush cr // Drupal Console (include clearing cache) $ drupal mou javascript_custom_module $ drupal moi javascript_custom_module We can see now from the Console of your browser the result of the execution of our first JavaScript code, just going to the declared route:\nWe\u0026rsquo;ve made our first interaction with JavaScript in Drupal! Well, now we are going to continue adding new JS cases, and then we will come back to this same initial case to continue iterating and looking at more and more available functionality.\nFollowing this simple initial exercise, we can check the operation of basic JavaScript methods such as an alert window or a confirmation window through the integration of libraries using the #attached property:\n3.4.2- Libraries in a TWIG template To add libraries to a Twig template within our project, either for a custom template within our own module or in a specific Twig template of the Theme we are using, we will load it through the Twig attach_library() function that lets us add directly to the template:\n{% block salute %} {% if salute_list is not empty %} {{ attach_library('custom_module_name/library_name') }} \u0026lt;div class=\u0026quot;salute__wrapper layout-container\u0026quot;\u0026gt; {{ parent() }} \u0026lt;/div\u0026gt; {% endif %} {% endblock salute %} But the truth is that it can cause problems in the rendering (that it does not arrive in time to load in the rendering cycle of the Render system that is put in motion when \u0026ldquo;painting\u0026rdquo; a page) if it is added to the global template html.html.twig . This is a debate that has been going on for some time: https://www.drupal.org/node/2398331#comment-9745117 and is also a subject for discussion with a view to changing the way libraries are loaded in the near future of Drupal: https://www.drupal.org/project/drupal/issues/3050386. So beware of the template you use it on that might not work and pay attention to changes that might come in new versions of Drupal.\n3.4.3- Global libraries for a Theme To declare your library as a global dependency for your Theme or your custom module, just include it in the declarative file of the *.info.yml resource using the libraries property:\n# resource.info.yml libraries: - module/library In any case and as in the previous section, there are discussions about the evolution of this and some measures that are supposed to be taken for future versions: https://www.drupal.org/node/1542344. The advice remains the same: Pay attention to possible changes.\n3.4.4- Adding libraries from Hooks It is also possible to add new custom libraries in our Drupal context, specifically before the time of rendering existing pages, through pre-processing hooks, such as hook_page_attachments(), which still maintains the already seen way of adding resources:\n// Form: $attachments['#attached']['library'][] = 'module/library'; Using a basic scheme for use:\n/** * Implements hook_page_attachments(). */ function custom_page_attachments(array \u0026amp;$attachments) { $attachments['#attached']['library'][] = 'module/library'; } Another option in hooks is the hook_preprocess_HOOK() function that according to its documentation, makes it easier for modules to preprocess theming variables for various elements. Let\u0026rsquo;s see a couple of examples:\n/** * Implements hook_preprocess_HOOK() for menu. */ function theme_name_preprocess_menu(\u0026amp;$variables) { $variables[‘#attached’][‘library’][] = ‘theme/library’; } The execution of this previous hook will make Drupal go to menu.html.twig and perform the addition of the differentiated library. Furthermore, this resource can be used in a generic way (for example, for all pages):\n/** * Implements hook_preprocess_HOOK() for page. */ function custom_theming_preprocess_page(\u0026amp;$variables) { $variables['#attached']['library'][] = 'module/library'; } In this case it is recommended to specify metadata to facilitate the caching of the new change, specifically if the aggregation operation of the new library depends on conditions, for example:\n/** * Implements hook_preprocess_HOOK() for page with conditions. */ function custom_theming_preprocess_page(\u0026amp;$variables) { $variables['page']['#cache']['contexts'][] = 'route'; $route = \u0026quot;entity.node.preview\u0026quot;; if (\\Drupal::routeMatch()-\u0026gt;getRouteName() === $route) { $variables['#attached']['library'][] = 'module/library'; } } And for more specific resources:\n/** * Implements hook_preprocess_HOOK() for maintenance_page. */ function seven_preprocess_maintenance_page(\u0026amp;$variables) { $variables[‘#attached’][‘library’][] = ‘theme/library’; } 4- Just a little bit more of JavaScript in Drupal Let\u0026rsquo;s take a closer look at the rules of use and integration of JavaScript code in a Drupal project.\n4.1- Structure and guidelines for IIFE The first thing that should call our attention is the fact that the structure of the .js extension file that we have introduced in our project through the /js folder has the following structure:\n(function () { 'use strict'; // Put here your custom JavaScript code. console.log (\u0026quot;Hello World\u0026quot;); })(); In Drupal, all our JavaScript code will be integrated within a closure function, as a wrapper of the code based on the IIFE pattern, that is, the \u0026ldquo;Immediately Invoked Function Expression (IIFE)\u0026rdquo; model, used as a useful structure for three key issues:\n First, it allows immediate execution (or self-execution). Second, it limits the scope of internal variables: does not alter other JavaScript codes present in the project. Third, The context execution of the IIFE is created and ends up destroying it automatically: it frees up memory space, and releases it quickly.  How is this achieved? Well I think we can understand the IIFE model in an intuitive way in four steps. Let\u0026rsquo;s see:\n We can create a function in JavaScript as normal:  function myFunction() { // Here your JavaScript code. } This function may or may not have a name (being an anonymous function) but in this case must be assigned to a variable:  // Function with name: function myFunction(){ // Here your JavaScript code. } -\u0026gt; Right // Anonymous function assigned to a variable: var myFunction = function() { // Here your JavaScript code. } -\u0026gt; Right // Anonymous function being not assigned to a variable: function() { // Here your JavaScript code. } -\u0026gt; JavaScript error So JavaScript does not allow us to execute the function, because after the keyword \u0026ldquo;function\u0026rdquo; it waits for a name that it cannot find.\nThis can be avoided by introducing the anonymous function in parentheses (well actually just by putting a sign in front of it would already serve but we adopt this consensus of the parentheses as a style guideline). This makes the JavaScript engine consider it an expression, or Function Expression (instead of Function Statement, with a name):  (function() { // Here your JavaScript code. }) The function remains in memory but nobody is using it. How do we execute it? Well, we can use the final parenthesis to call its execution:  (function() { // Here your JavaScript code. })() -\u0026gt; It's only a guideline, since this algo serves: (function() { // Here your JavaScript code. }()) -\u0026gt; We've passed the invocative parentheses into the expression. In fact, if we enter parameters in the execution brackets, the function will treat them with absolute normality. We will see an example later on through a small exercise (Ex. 5: Passing values to the IIFE format).\nBesides, as it is an anonymous function, it can be used as an \u0026ldquo;arrow function\u0026rdquo;:\n(() =\u0026gt; { // Here your JavaScript code. // })() The latter are the forms that our JavaScript code can take in Drupal. Remember that whatever the style guideline we choose, we always need to comply with two fundamental guidelines:\n They are built in a compartmentalized way, without \u0026ldquo;contaminating\u0026rdquo; any global object, that is, the global execution space (that the variables only live inside their function, like a private code block). They are executed immediately, destroyed and cannot be executed again (if a page is reloaded, they are requested again).  4.2- Passing parameters in IIFE We are going to makechanges on the rendered HTML of our Drupal through our custom module, for which we must first assign a custom selector to the element we want to modify.\nExercise 5: Passing values to the IIFE format We start by going back to the controller class file and adding two new Drupal element rendering system properties: #prefix and #suffix which allow an HTML element to be framed within other HTML tags. In this case we want to add our own id to the element.\n// Line 42. $final_array['welcome_message'] = [ '#type' =\u0026gt; 'item', '#markup' =\u0026gt; $this-\u0026gt;t('Hello World, I am just a text.'), '#prefix' =\u0026gt; '\u0026lt;div id=\u0026quot;salute\u0026quot;\u0026gt;', '#suffix' =\u0026gt; '\u0026lt;/div\u0026gt;', '#attached' =\u0026gt; [ 'library' =\u0026gt; [ 'javascript_custom_module/js_hello_world_console', ], ], ]; Next we create a new .js file (\u0026lsquo;iife_salute_example.js\u0026rsquo;)with a function in IIFE format. To this function we will pass a text string as a greeting for our users (\u0026lsquo;Dear User\u0026rsquo;), and we will declare the input parameter in its definition (\u0026lsquo;parameter\u0026rsquo;).\n(function (parameter) { 'use strict'; // Get the HTML element by it ID. let element = document.getElementById(\u0026quot;salute\u0026quot;); console.log(element); // Add to the HTML the new string using the parameter. element.innerHTML += \u0026quot;Salute, \u0026quot; + parameter; // Creating and adding a line for the HTML element. var hr = document.createElement(\u0026quot;hr\u0026quot;); console.log(hr); element.prepend(hr); })('Dear User'); We\u0026rsquo;ll introduce some changes with pure JavaScript, like adding a text to the message of the HTML element, taking the value of the text string passed by parameter. Then we also put a dividing line over the element, as a separator.\nWe added the new file to the library resources that we had already defined previously:\njs_hello_world_console: js: js/hello_world_console.js: {} js/iife_salute_example.js: {} And so, if we clean the drush cr cache and reload the /javascript/custom path in the browser, we will be able to see the new changes made using JavaScript:\n4.3- Passing values from PHP to JavaScript: drupalSettings We have seen in the previous section how to pass values to that IIFE within the revision of the structure and operation of this JavaScript code format and now we are going to stop at a very particular construction that is available for us to make connections between our server executable code (PHP) and our client executable code (JavaScript) within Drupal: let\u0026rsquo;s talk about drupalSettings.\nLet\u0026rsquo;s think about implementing a slightly more particular greeting to the user who visits our url /javascript/custom . We want to extract data about the visitor\u0026rsquo;s identity to give them a more personal greeting. Ok. We can extract this information inside our Controller through the service current_user: api.drupal.org/core.services.yml/current_user/9.0.x, which offers us methods to obtain this information. We want to take this information into the code that runs on the client, so we will transfer it to JavaScript.\nWe were including the current_user service in the Controller, between lines 24 - 29 of the source code:\n public static function create(ContainerInterface $container) { return new static( $container-\u0026gt;get('current_user'), $container-\u0026gt;get('database') ); } So you will can use the service from the Controller using a class property, the so called $this-\u0026gt;current_user.\nWe can transfer it all through drupalSettings, a sub-property available for the property #attached , which is received from the JavaScript side through the drupalSettings object, which will have the values available as new properties. Let\u0026rsquo;s see the next exercise.\nExercise 6: Transfering values trough drupalSettings We will create a new JavaScript file for a more particular greeting, called hello_world_advanced.js. On the one hand, we\u0026rsquo;re extracting the information and adding the new library from the PHP side:\n// We're adding the new resources to the same welcome element. $final_array['welcome_message']['#attached']['library'][] = 'javascript_custom_module/js_hello_world_advanced'; $final_array['welcome_message']['#attached']['drupalSettings']['data']['name'] = $this-\u0026gt;current_user-\u0026gt;getDisplayName(); $final_array['welcome_message']['#attached']['drupalSettings']['data']['mail'] = $this-\u0026gt;current_user-\u0026gt;getEmail(); On the other hand, we\u0026rsquo;re getting the values from the JavaScript side:\n(function () { 'use strict'; // Recovering the user name and mail from drupalSettings. let element = document.getElementById(\u0026quot;salute\u0026quot;); let user_name = drupalSettings.data.name; let user_mail = drupalSettings.data.mail; // Add to the HTML the new strings. element.innerHTML += \u0026quot;Update-\u0026gt; You are the user: \u0026quot; + user_name + \u0026quot; with mail: \u0026quot; + user_mail; })(); Now, adding the library drupalSettings (from the Drupal core) as a new dependency, we can start connecting variables between PHP and JavaScript. We will change our library definition file to define a new custom resource that will use this new dependency:\njs_hello_world_advanced: js: js/hello_world_advanced: {} dependencies: - core/drupalSettings So we can see the new values loaded both from the web rendering and from the drupalSettings object itself, through the console (drupalSettings.data, remember):\nReady!\n4.4- Changes in rendered HTML We will use this section to extend functionally our custom module for JavaScript by implementing some simple and interesting features, to continue practicing with JavaScript in the context of Drupal and to standardize its use in our projects.\n4.4.1- Counting visits using Web Storage Let\u0026rsquo;s see\u0026hellip; Do you know the concept of \u0026ldquo;Web Storage\u0026rdquo;? Well, in short, it\u0026rsquo;s a small HTML API available in modern browsers to store information internally through two mechanisms: Session Storage (for information maintained only in the context of the open page session) and Local Storage (to persist information until we explicitly remove it).\nRead more about the web storage API at: developer.mozilla.org/Web_Storage_API\nHere, for example, you can check the availability and capacity (usually around 5MB) of your web browser for web storage (Local and Session): http://dev-test.nemikor.com/web-storage/support-test/.\nExercise 7: Custom visit counter with JavaScript In this step we will create a small and persistent visit counter to inform the user of the number of times he or she has loaded our custom /javascript/custom/ route.\nFirst, we ask for the current values:\n// Asking for the localStorage parameter value if exists. let visit_value = localStorage.getItem('visit_number'); console.log(\u0026quot;LocalStorage - current value: \u0026quot; + visit_value); // Same but for the sessionStorage. let session_value = sessionStorage.getItem('session_number'); console.log(\u0026quot;SessionStorage - current value: \u0026quot; + session_value); Then we check if they are already created and initialized. Just a little intuitive game. If they are null, we create them and load them with an initial value equal to one. If they already exist we increase them and load them again updated. We take advantage of this to display them through the console:\n// Testing the localStorage visit value. if(visit_value === null) { // If null we'll create the initial value. localStorage.setItem('visit_number', 1); console.log(\u0026quot;LocalStorage: \u0026quot; +localStorage.getItem('visit_number')); }else { // If not null we'll increment the current value. localStorage.setItem('visit_number', ++visit_value); console.log(\u0026quot;LocalStorage: \u0026quot; + localStorage.getItem('visit_number'); } // Same for sessionStorage. if(session_value === null) { // If null we'll create the initial value. sessionStorage.setItem('session_number', 1); console.log(\u0026quot;Session: \u0026quot; + sessionStorage.getItem('session_number')); }else { // If not null we'll increment the current value. sessionStorage.setItem('session_number', ++session_value); console.log(\u0026quot;Session: \u0026quot; + sessionStorage.getItem('session_number')); } At the end, we take the opportunity to display the counter values in the HTML of the page:\n// Add to the HTML the counter value. element.innerHTML += \u0026quot;\u0026lt;br\u0026gt;\u0026quot; + \u0026quot;Total visits: \u0026quot; + localStorage.getItem('visit_number'); element.innerHTML += \u0026quot;\u0026lt;br\u0026gt;\u0026quot; + \u0026quot;Total visits during this session: \u0026quot; + sessionStorage.getItem('session_number'); And when the address is reloaded, it shows the registration values via the Web Storage API:\nDid you know about this little storage API? and what other ideas do you have that could be implemented using it?\n5- Drupal and the old jQuery According to its own mission:\n \u0026ldquo;The purpose of jQuery is to make it much easier to use JavaScript on your website.\u0026quot;\n  Source: jQuery Official documentation: https://jquery.com  And so it has been for many years. It is, in short, a JavaScript library created to offer a standardized way (or something like that) to interact with the elements of the Document Object Model (DOM) in the simplest and most direct way possible.\njQuery has -at the time of writing- fourteen years of life since its first published version and extensive use throughout all the websites published on the Internet. Without falling into technological holy wars, we will just assume that it is still present (for now) in the development of Drupal and that several versions and formats of jQuery are offered within the platform. We will see how to use it and how to relate to it in a (relatively) efficient way.\n5.1- Fast review of the jQuery keys As this article is not by itself a jQuery tutorial and I\u0026rsquo;m afraid that at the end the extension of it will exceed twelve thousand words, you will excuse me for not stopping too much here. jQuery requires another manual of the same (or higher) extension. So let\u0026rsquo;s give some context through some basic keys and we\u0026rsquo;ll go on. Pay attention.\nRemember:\n  In jQuery, $ is an alias for jQuery.\n  Usually, jQuery starts when the document is fully loaded, through the instruction: $(document).ready(function(){ // }.\n  jQuery offers thousands of ways to interact with HTML elements, from selectors through the element id (#id), its CSS class (.class), HTM tag names (\u0026ldquo;div\u0026rdquo;), or attribute values (name = value). The list and its options is endless and it is convenient to have it somewhat tested: https://api.jquery.com/category/selectors.\n  With the management of its selectors, you will be able to make changes at several levels in your HTML: CSS styles, add/alter/remove elements, add visual effects, make callbacks and Ajax requests. For all this you will use jQuery (perhaps).\n  And don\u0026rsquo;t forget to consider jQuery\u0026rsquo;s recommendations for good use. See this set of guidelines, quite old but interesting: http://lab.abhinayrathore.com/jquery-standards.\n5.2- Availability of jQuery in our Drupal version From Drupal 8 onwards, was changed the system for loading libraries and resources, causing nothing (or almost nothing) to be loaded by default.This, among other things, implies that jQuery is not included in every page unless you request it as a dependency for your resource (a library dependency for your module or theme, declared as we have already seen).\nAt this moment, all the libraries related to jQuery are declared in advance but they will only be preloaded if you need them. These libraries can be located in the /core/core.libraries.yml file:\nWhere you can see from line 350 of the file the list of jQuery libraries associated to Drupal\u0026rsquo;s core. As you can see, there are many jQuery libraries declared, some of them to be explicitly requested as dependencies in custom resources (modules or themes) and others for internal consumption, since sometimes, Drupal uses underneath jQuery plugins to build elements like buttons, navigation tabs and other resources.\nHere is a graph prepared in 2015 by Théodore Biadala, @nod_ about the extensive use Drupal makes of jQuery (a little outdated, is from 2015): http://read.theodoreb.net/viz-drupal-use-of-jquery.\n5.3- Using a different version of jQuery Let\u0026rsquo;s suppose that for some specific needs of the project, we want to use a different version of jQuery than the ones supported within our version of Drupal, what to do? (asked the wise man). Well, we can add it as a resource to our project without problems through the guidelines we already know:\njquery-custom: remote: https://github.com/jquery/jquery version: \u0026quot;2.2.4\u0026quot; license: name: MIT url: https://github.com/jquery/jquery/blob/2.2.4/LICENSE.txt gpl-compatible: true js: js/jquery-2.2.4.min.js: { minified: true, weight: -20 } And then we can overwrite the dependency from its declaration in the file my_custom_resource.info.yml:\nlibraries-override: # Replace the entire library. core/jquery: my_custom_resource/jquery-custom Exercise 8: Changes based on jQuery We will perform a couple of exercises using jQuery in our custom module.\n Loading text Lorem Ipsum via AJAX  After the previous exercises with JavaScript, if we close all the windows we have now, we will stay in our /javascript/custom route alone with our table of results showing comments associated with the current user, which was:\nWe will provide an introductory text to the page through the consumption of an external API that will provide us with Lorem Ipsum paragraphs. We will declare the new dependency in the usual *.libraries.yml file:\njs_playing_with_jquery: js: js/playing_with_jquery: {} dependencies: - core/jquery In this case we will try to load the new library through a hook of type hook_page_attachments() inside the file javascript_custom_module.module:\n/** * Implements hook_page_attachments(). */ function javascript_custom_module_page_attachments(array \u0026amp;$attachments) { // Getting the current route name. $route_name = \\Drupal::routeMatch()-\u0026gt;getRouteName(); // Load the library only if match with the selected page by route. if (strcmp($route_name, 'javascript_custom.hello') == 0) { $attachments['#attached']['library'][] = 'javascript_custom_module/js_playing_with_jquery'; } } And in the folder js/ we will create the new file playing_with_jquery.js , in which we will dump all our mandanga.\nLet\u0026rsquo;s start by adding some introductory text to the page. to do this we\u0026rsquo;ll make a request to the web baconipsum through its API, for which we will use the jQuery function $.getJSON() that handles three parameters: a URL address, some data to build the request and a callback function in case the request is successful. This itself is a wrapper provided by jQuery to handle as a HTTP GET verb request in a JSON format: api.jquery/getJSON.\nLet\u0026rsquo;s see what we can do: First we will add a new HTML container for the texts (\u0026lt;div id='bacon-text'\u0026gt;), then we will make the request, getting the results and loading a new paragraph (\u0026lt;p\u0026gt;) into the newly created container.\n(function ($) { 'use strict'; $(document).ready(function(){ console.log(\u0026quot;The Playing with jQuery script has been loaded\u0026quot;); $('#block-bartik-page-title').append(\u0026quot;\u0026lt;div id='bacon-text'\u0026gt;\u0026lt;/div\u0026gt;\u0026quot;); // Calling AJAX. $.getJSON('https://baconipsum.com/api/?callback=?', { 'type':'meat-and-filler', 'start-with-lorem':'1', 'paras':'4' }, function(baconTexts) { if (baconTexts \u0026amp;\u0026amp; baconTexts.length \u0026gt; 0) { $(\u0026quot;#bacon-text\u0026quot;).html(''); for (var i = 0; i \u0026lt; baconTexts.length; i++) $(\u0026quot;#bacon-text\u0026quot;).append('\u0026lt;p\u0026gt;' + baconTexts[i] + '\u0026lt;/p\u0026gt;'); $(\u0026quot;#bacon-text\u0026quot;).show(); } }); }); })(jQuery); But let\u0026rsquo;s give it some movement thanks to the bizarr errrr\u0026hellip;dynamic functions provided by jQuery. We are going to rethink a little this initial script to make a progressive loading of the Bacon Ipsum welcome paragraphs.\nFirst of all, we will put a button. We\u0026rsquo;ve already stained the rendered page too much and we\u0026rsquo;re going to leave the view clean before playing with bacon:\n// Creating the new elements just a div and a button. $('#block-bartik-page-title').append(\u0026quot;\u0026lt;input type='button' id='getting-bacon' class='btn-bacon' value='Bacon' /\u0026gt;\u0026quot;); $('#block-bartik-page-title').append(\u0026quot;\u0026lt;div id='bacon-text'\u0026gt;\u0026lt;/div\u0026gt;\u0026quot;); Next, we will add a click event to that button so that when it is pressed, it will start processing bacon:\n// Adding a click event to the former button. $('#getting-bacon').click(function () { // Processing bacon. }); In case we already have bacon loaded, we take care of cleaning the div:\n// Hidding the block for the next load. $(\u0026quot;#bacon-text\u0026quot;).hide(); And we go ahead to process out bacon requests:\n// Getting values in JSON format. $.getJSON('https://baconipsum.com/api/?callback=?', {'type': 'meat-and-filler', 'start-with-lorem': '1', 'paras': '4'}, function (baconTexts) { // We're in the callback function for success in JSON request. if (baconTexts \u0026amp;\u0026amp; baconTexts.length \u0026gt; 0) { $(\u0026quot;#bacon-text\u0026quot;).html(''); // Loop into the received items. for (var i = 0; i \u0026lt; baconTexts.length; i++) { // Creating the naming keys. var bacon_id = \u0026quot;bacontext_\u0026quot; + i; var new_bacon = \u0026quot;\u0026lt;p\u0026quot; + \u0026quot; id='\u0026quot; + bacon_id + \u0026quot;'\u0026quot; + \u0026quot;\u0026gt;\u0026quot; + baconTexts[i] + \u0026quot;\u0026lt;/p\u0026gt;\u0026quot;; // Add the new element to the div mother. $(\u0026quot;#bacon-text\u0026quot;).append(new_bacon); } } }); To make the subject a bit more dynamic, we added one of jQuery\u0026rsquo;s less poisono\u0026hellip;emm\u0026hellip;more discreet animations with a confirmation message and the .slideDown() function from jQuery, which vertically scrolls the content from top to bottom:\n// Show the div mother show in a progressive way. $(\u0026quot;#bacon-text\u0026quot;).slideDown(7000, function(){ console.log(\u0026quot;New bacon has been loaded\u0026quot;); }); And when you reload everything, you see the completeexecution of all the JavaScript on the page:\nHere you have the code formatted as a gist:\n 6- Drupal Behaviors In this guide, we already know how to integrate JavaScript in our modules and projects, how to create interactions, passing parameters between PHP (server) and JavaScript (client), integrating jQuery in our dependencies and as a final step to prepare the last step that should integrate all the above, we must talk about the concept of \u0026ldquo;Drupal Behaviors\u0026rdquo;.\nWhat is a Behavior? It\u0026rsquo;s the organized way that Drupal offers us to add and index behaviors based on JavaScript, through the extension of an own hook_behavior object that is part of another global Drupal JavaScript object.\n6.1- Anatomy of a Behavior We will review the basic functional structure of the Behavior itself, as this format becomes the essential form of Drupal\u0026rsquo;s JavaScript integration and it is in our interest to know its parts first. Let\u0026rsquo;s see.\nLet\u0026rsquo;s have a look.\n  namespace: A Drupal behavior has to have a specific and unique name to be located, identified, executed and removed. It will become part of the Behaviors object and will be indexed there. In this case it is simply named \u0026ldquo;namespace\u0026rdquo;.\n  attach: This is the function to be executed as soon as the Behavior is loaded. For the executions of Behaviors, it will be gone through the indexed behaviors and for each one will be called its function\u0026quot;attach\u0026rdquo;, each one doing what it has to do.\n  detach: As when adding, a function is provided to be executed when the behaviour is removed from the behaviour log.\n  context: It\u0026rsquo;s a variable where the piece of the page that is being transformed is loaded. In an initial loading of the page, it will be the complete DOM, in AJAX operations it will be the corresponding HTML piece. This variable helps us to tune up more with our operations, so we must have clear how to handle it.\n  settings: This variable we\u0026rsquo;re seeing in the screenshot is used to transfer values from the PHP code to JavaScript and make them available in the form we saw earlier from our code. To do this we must declare the core/drupalSettings as a dependency of our JavaScript library.\n  trigger: The trigger variable that is passed to the function associated to detach represents the condition for the deactivation of the behavior, where some causes are admitted:\n   unload: This is the default reason, it means that the context element has been removed from the DOM. serialize: For forms with AJAX, where this variable is used to send the form itself as context. move: The element has been moved from its position in the DOM from its initial location.  jQuery: In this case, this point just represents the passage of parameters to the IIFE, usually (jQuery, Drupal) as integrated dependencies available for our code.  6.2- The global object \u0026ldquo;Drupal\u0026rdquo; As stated in the official Drupal documentation, Drupal.behaviors is an object that is by itself part of the global JavaScript object \u0026ldquo;Drupal\u0026rdquo;, created for the entire running Drupal instance. This was a concept already used and exploited in previous versions of Drupal, with some aspects remaining over time.\nThe main one: that the modules that want to implement JavaScript must do so by adding logic to the Drupal.behaviors Object. Let\u0026rsquo;s see how, and let\u0026rsquo;s know the basis of Behaviors: the global object \u0026ldquo;Drupal\u0026rdquo;.\nIf you know the concept of \u0026ldquo;Object\u0026rdquo; in JavaScript, you will know that it\u0026rsquo;s an advanced way of handling data in JavaScript, and basically, it consists of a disordered collection of related information: primitive data types, values in properties, methods\u0026hellip; everything designed under a basic structure of key pairs: value.\n// Basic example for a JavaScript Object. let drupal_event= { name: 'Drupal Camp Spain 2020', location: 'Málaga', original_location: 'Barcelona', established: '2010', displayInfo: function(){ console.log(`${drupal_event.name} was established in ${drupal_event.established} at ${drupal_event.original_location}`); } } // Shows feedback by Console. drupal_event.displayInfo(); This object is perfectly executable in the JavaScript console of your browser, and will work as expected:\nRead more about objects and properties in JavaScript: geeksforgeeks.org/objects-in-javascript/.\nObjects in JavaScript can be browsed, modified, deleted and above all (for the reasons we are dealing with now), extended. This is exactly what will happen with our new friend, the global object \u0026ldquo;Drupal\u0026rdquo;, an existing resource -always- in any Drupal site installed from the drupal.js library present in the /core/misc/ path:\nHere in the previous image we see the file (a fundamental script in Drupal), which serves to provide centrally various JavaScript APIs in Drupal and to provide a common namespace to group all the extensions that will be added to the global object. In fact, if you call the global Drupal object, you will be able to see the base content it brings:\nOf all the previous list, perhaps it is Drupal.behaviors and its related methods (attachBehaviors, detachBehaviors) that are most important to us now, although we should point out some interesting utilities:\n The Drupal.t function, which is equivalent to the t() translation function for translations in Drupal: theodoreb.net/drupal-jsapi/Drupal.html#.t The small Drupal.dialog API, which simulates the dialog window element of HTML5: theodoreb.net/drupal-jsapi/Drupal.html#.dialog#~dialogDefinition Drupal.theme, to invite to process any HTML answer that should be submitted to theming (it is extended by many contrib libraries, like ckeditor): theodoreb.net/drupal-jsapi/Drupal.theme.html.  Well, we\u0026rsquo;ve already seen a little piece of theory to gain context\u0026hellip;it\u0026rsquo;s time to practice a little. Let\u0026rsquo;s extend what we already know how to do with a new exercise:\nExercise 9: Dialog window from the global object \u0026ldquo;Drupal\u0026rdquo; We will take the Drupal dialog API as a reference to build a window into our project through our custom module. To begin with, we are going to register a new library in our custom javascript_custom_module module, inside the javascript_custom_module_libraries.yml file, which will now look like this:\njs_hello_world_console: js: js/iife_execution_example.js: {} js/hello_world_console.js: {} js/iife_salute_example.js: {} js_hello_world_advanced: js: js/hello_world_advanced: {} dependencies: - core/drupalSettings js_custom_dialog_window: js: js/custom_dialog_window: {} dependencies: - core/drupal - core/jquery - core/drupalSettings Next we load the new library as #attached in our render array returned by the Controller, from line 55 in the file CommentsListController.php :\n$final_array['welcome_message']['#attached']['library'][] = 'javascript_custom_module/js_custom_dialog_window'; And we\u0026rsquo;ll build a very basic modal window, based on pure JavaScript. This dialogue will only have a simple message and a button to interact, in which we will include a style change on the element containing the message.\nLet\u0026rsquo;s see the new file custom_dialog_window.js :\nfunction () { 'use strict'; // Put here your custom JavaScript code. // First creating and initialising the new element. let new_tag = document.createElement(\u0026quot;P\u0026quot;); new_tag.setAttribute(\u0026quot;id\u0026quot;, \u0026quot;my_p\u0026quot;); new_tag.innerHTML = \u0026quot;Hello World from a custom Dialog Window.\u0026quot;; // Then we'll creating a new modal window. Drupal.dialog(new_tag, { title: 'Custom Dialog Window', buttons: [{ text: 'Change colour', click: function() { let change_colour = document.getElementById(\u0026quot;my_p\u0026quot;); change_colour.style.backgroundColor = \u0026quot;red\u0026quot;; } }] }).showModal(); })(); You can review all the JavaScript associated with the global object \u0026ldquo;Drupal\u0026rdquo; thanks to the great documentation Théodore Biadala (@nod_) published years ago about the Drupal JavaScript API:\nhttp://read.theodoreb.net/drupal-jsapi/index.html\n6.3- Behaviors in Drupal In a previous section, we already saw how to run jQuery in our code. We also know that it is important to check if the document (DOM) has already been fully loaded before starting to perform actions. Basically:\n(function ($) { 'use strict'; $(document).ready(function() { // Put here your jQuery code. }); })(jQuery) But let\u0026rsquo;s think carefully about this execution: it will be performed when the DOM has been loaded completely (at an initial moment), but it will not make adjustments after a partial loading of the DOM (for example, after an AJAX execution that modifies only a portion of the DOM). We need another idea. See the next example:\n(function ($, Drupal ) { 'use strict'; // Put here your custom JavaScript code. Drupal.behaviors.unsplash_connector = { attach: function (context, settings) { console.log(\u0026quot;Loaded Unsplash Behavior\u0026quot;); }, detach: function (context, settings, trigger) { // JavaScript code. } } })(jQuery, Drupal); This code, when executed, will make several print calls in Console (in this case, up to three times):\nWhy is this? Well, as we can see using breakpoints in the JavaScript debugging console of our phavorite browser, the loading of behaviors by the global Drupal object is done several times during the loading process of a single link: in this case there is a \u0026ldquo;full\u0026rdquo; loading of the DOM and several \u0026ldquo;partial\u0026rdquo; reloads through AJAX. In each case, a processing of behaviors is done through the method:\nDrupal.attachBehaviors (line 17, library drupal.js) Which loads a function that runs through all the behaviours and executes them according to their context and parameters:\nThe next step is to put some control on the execution of the instruction, passing it from an active mode (that writes in the console just when loading) to a reactive mode (that writes only when an interaction takes place):\n(function ($, Drupal ) { 'use strict'; // Put here your custom JavaScript code. Drupal.behaviors.unsplash_connector = { attach: function (context, settings) { $('#unsplash_section', context).click(function() { console.log(\u0026quot;Loaded Unsplash Behavior\u0026quot;); }); }, } })(jQuery, Drupal); So now we have placed over the ID selector of our welcome message a click control event, which when clicked loads a message into the console:\nWith this small example above, we have seen how to add a small event-based (click) functionality. Let\u0026rsquo;s go on to do something more interesting.\nExercise 10: Image Board from Unsplash using Drupal.behavior We will implement a functionality that operates by consuming an external API through Drupal Behavior.\nWe are going to practice with a slightly more advanced (and more beautiful) idea: we will connect to the public API for applications of an online image stock service from a new Drupal Behavior and from there we will make image requests that we\u0026rsquo;ll show then from a custom image board in our Drupal.\nWhat do we need? Well for this recipe we will need the following ingredients:\n An account for application access to the Unsplash API, you can do it here https://unsplash.com/developers and extract the request URL https://api.unsplash.com/search/photos and your private access key to make requests. You must register as an API user, register a new app and extract the key:    A new JavaScript library within our custom module with its own .js file to store this Behavior:\n  A new route set declared in the routing file, a new controller class and a method that generates a render array as response:\n  To facilitate the following integrations, we are going to add to the render array a couple of properties (#prefix, #suffix) to add a new \u0026lt;div\u0026gt; with a own id = unsplash (see the image above).\nNow with these ingredients, we\u0026rsquo;ll start. First we create the skeleton of our Behavior and define what we only want to be loaded once (and not reloaded with AJAX):\n(function ($, Drupal) { 'use strict'; Drupal.behaviors.getting_unsplash_items = { attach: function(context, settings) { $(context).find(\u0026quot;#unsplash\u0026quot;).once('unsplashtest').each(function() { // All our new functionality. }); } }; })(jQuery, Drupal); Remember: the term we provide to jQuery.once() is totally random and non-repeatable, just to trace internally that the action already happened.\nFirst part: We create a welcome message and two buttons: one to start an image search process and another one to clean the image board generated from the search and the results.\n// Adding the buttons through jQuery. $(\u0026quot;#unsplash\u0026quot;, context).append(\u0026quot;\u0026lt;button type='button' id='load_button'\u0026gt;Load Images\u0026lt;/button\u0026gt;\u0026quot; ); $(\u0026quot;#unsplash\u0026quot;, context).append(\u0026quot;\u0026lt;button type='button' id='clean_button'\u0026gt;Clean Board\u0026lt;/button\u0026gt;\u0026quot; ); // Adding an event listener to the new buttons using jQuery. $('#load_button').click(function() { // In case of click we will clean the former message. $(\u0026quot;#message\u0026quot;, context).remove(); // In case of click we will call to the prompt window. processingKeywords(); }); // Adding a second event listener to the clean button. $('#clean_button').click(function() { // In case of click we will clean the written former message. $(\u0026quot;#message\u0026quot;, context).remove(); // And we will remove the entire image board too. $(\u0026quot;#image-board\u0026quot;).remove(); }); As we can see in one of the previous calls, the image search process from the introduction of a keyword begins to be delegated to functions, started by the processingKeywords() function and we launch a prompt to capture the keyword and make sure to check if empty terms are being accepted:\nfunction processingKeywords(){ let message = ''; let option = prompt(\u0026quot;Please write a keyword for search: \u0026quot;, \u0026quot;boat\u0026quot;); if(option == null || option == \u0026quot;\u0026quot;){ // Null option without response. message = \u0026quot;Sorry but the keyword is empty.\u0026quot;; // Render in screen the message from the prompt. $(\u0026quot;#unsplash\u0026quot;, context).append(\u0026quot;\u0026lt;br/\u0026gt;\u0026lt;p id='message'\u0026gt;\u0026quot; + message + \u0026quot;\u0026lt;/p\u0026gt;\u0026quot;); }else { // Valid answer launches request. message = \u0026quot;Ok, we're searching...\u0026quot; + option; // Render in screen the message from the prompt. $(\u0026quot;#unsplash\u0026quot;, context).append(\u0026quot;\u0026lt;br/\u0026gt;\u0026lt;p id='message'\u0026gt;\u0026quot; + message + \u0026quot;\u0026lt;/p\u0026gt;\u0026quot;); // Launching external request with some delay with arrow format. setTimeout(() =\u0026gt; { gettingImages(option); }, 4000); } } And we call the function responsible for managing the requests, gettingImages(), with the keyword as a parameter. We will use async / await to avoid problems of uninitialized variables in case the service was delayed. We also give a little delay to the call of the next function.\nasync function gettingImages(keyword){ // Loading basic values Access Key and End Point. const unsplash_key = 'YOUR APP KEY'; const end_point = 'https://api.unsplash.com/search/photos'; // Building the petition. let response = await fetch(end_point + '?query=' + keyword + '\u0026amp;client_id=' + unsplash_key); // Processing the results. let json_response = await response.json(); // Getting an array with URLs to images. let images_list = await json_response.results; // Calling the createImages method. creatingImages(images_list); } At last we\u0026rsquo;ll invoke the function that will take the image address list and we will build the corresponding HTML tags:\nfunction creatingImages(images_list) { // If a former image board exists we will delete it. $(\u0026quot;#image-board\u0026quot;, context).remove(); // Creating a new image board as frame for the images. $(\u0026quot;#unsplash\u0026quot;, context).append(\u0026quot;\u0026lt;section id='image-board'\u0026gt; \u0026lt;/section\u0026gt;\u0026quot;); // We will add some CSS classes for styling the image board. $(\u0026quot;#image-board\u0026quot;).addClass(\u0026quot;images-frame\u0026quot;); // Now we will set the received images inside the new board. for(let i = 0; i \u0026lt; images_list.length; i++){ const image = document.createElement('img'); image.src = images_list[i].urls.thumb; document.getElementById('image-board').appendChild(image); } // When finished we will put a border for the image board. $(\u0026quot;.images-frame\u0026quot;).css({'background-color': '#babb8f', 'border': '5px solid #1E1E1E'}); } Note: If you are looking for information about the use of jQuery.once(), remember the transition in its use from Drupal 7 to Drupal 8 and 9 for the passage of functions as a parameter -\u0026gt;\n// Example of use in Drupal 7 $(context).find(\u0026quot;.element\u0026quot;).once(\u0026quot;random-key\u0026quot;, function () {}); // Example of use in Drupal 8 || 9 $(context).find(\u0026quot;.element\u0026quot;).once(\u0026quot;random-key\u0026quot;).each(function () {}); Read more about jQuery.once():\n Is jQuery .once() needed if we filter by context? https://github.com/robloach/jquery-once https://github.com/RobLoach/jquery-once/blob/master/API.md#readme  And so, if we go in our test Drupal on the path:\nhttp://drupal.localhost/unsplash/service We will already have available the new image board obtained from the Unsplash API and built from a Drupal Behavior:\nHere you have available the complete code of the Behavior that we have just implemented:\n 7- JavaScript without JavaScript: #ajax, #states It was necessary, at least, to make a review on these knowledge areas where JavaScript is of indirect handling and execution. It is there but it is not seen. The subject is so extensive and can reach a level that would require more articles about the topic, so I will limit myself to make a review of some keys and launch the \u0026ldquo;to be continued\u0026hellip;\u0026rdquo; for later (or maybe this article would never see the light).\n7.1- (Brief) Introduction to AJAX in Drupal The Ajax API in Drupal contains such an extensive set of classes, events, resources and possibilities that you can make several articles of the extension of it just about using Ajax. Due to the limitations regarding the extension of this tutorial, we will focus on some basic keys, leaving for later the possibility of preparing an article on more advanced issues.\nHere you can check it out the AJAX API in Drupal.\nWe can use, at a basic level, Ajax for three well known formulas:\n  In links: using the class \u0026lsquo;use-ajax\u0026rsquo; in a link, we can give you Ajax treatment.\n  In form elements: We can add Ajax events to our form elements by using the #ajax property within a render array definition.\n  In form buttons: adding the class \u0026lsquo;use-ajax-submit\u0026rsquo; in the element declaration, we will make a call with Ajax.\n  Let\u0026rsquo;s see one of its main uses in form elements. This is an example of AJAX actions to be performed from the change of option selected in a drop-down list, specifically one that allows you to select a region, so we are using AJAX like a trigger. In this case we\u0026rsquo;re adding the #ajax property to an element to change its options, so we can load some related properties and after that, we\u0026rsquo;ll create a new callback function:\n // Offers a select for Regions. $form['main_region'] = [ '#type' =\u0026gt; 'select', '#title' =\u0026gt; $this-\u0026gt;t('Select Region'), '#description' =\u0026gt; $this-\u0026gt;t('This will be your selected region for contact.'), '#options' =\u0026gt; $terms_options_2, '#weight' =\u0026gt; '8', '#prefix' =\u0026gt; '\u0026lt;div id=\u0026quot;contact_form_region\u0026quot;\u0026gt;', '#suffix' =\u0026gt; '\u0026lt;/div\u0026gt;', '#ajax' =\u0026gt; [ 'event' =\u0026gt; 'change', 'method' =\u0026gt; 'html', 'callback' =\u0026gt; '::loadRelatedOfficesCallback', 'wrapper' =\u0026gt; 'contact_form_office', 'progress' =\u0026gt; [ 'type' =\u0026gt; 'throbber', 'message' =\u0026gt; $this-\u0026gt;t('loading related offices'), ], ], ]; In this case I\u0026rsquo;m building a form using the Drupal Form API and I need some operations over a select element. Due to this, I\u0026rsquo;m adding a very specific block focused to AJAX:\n '#ajax' =\u0026gt; [ 'event' =\u0026gt; 'change', 'method' =\u0026gt; 'html', 'callback' =\u0026gt; '::loadRelatedOfficesCallback', 'wrapper' =\u0026gt; 'contact_form_office', 'progress' =\u0026gt; [ 'type' =\u0026gt; 'throbber', 'message' =\u0026gt; $this-\u0026gt;t('loading related offices'), ], ], Here I\u0026rsquo;m specifying a event (change), a method for the event (html), a callback, marking a wrapper (the div for the element that will be changed from this one) and at last some indicators for the AJAX processing: an icon of \u0026ldquo;loading\u0026rdquo; and a message for the user.\nWhat is happening in the callback? well, First we ask for the triggered element, by using $form_state-\u0026gt;getTriggeringElement(). So you can get the item. Other importante step is get the css selector marked in the triggered element, by using $triggeringElement[\u0026quot;#ajax\u0026quot;][\u0026quot;wrapper\u0026quot;].\n/** * Callback function ready to get offices renewing the HTML component. */ public function loadRelatedOfficesCallback(array \u0026amp;$element, FormStateInterface $form_state){ // Gets the initial values from the triggered element. $triggeringElement = $form_state-\u0026gt;getTriggeringElement(); // Gets more info or computed values. [...] // Gets the css selector to modify when callback ends. $wrapper_id = $triggeringElement[\u0026quot;#ajax\u0026quot;][\u0026quot;wrapper\u0026quot;]; // Executes changes and alterations. [...] // Creates a new AjaxResponse and adds a jQuery Command for replace the item. $response = new AjaxResponse(); $response-\u0026gt;addCommand(new ReplaceCommand('#' . $wrapper_id, $changed_value)); return $response; } From the former callback, only two lines are interesting: the creation of a new AjaxResponse, using the related class: api.drupal.org/class/AjaxResponse and the load of a new command for AJAX, using the action commands defined in the AJAX API of Drupal: drupal.org/ajax-api/core-ajax-callback-commands.\nThese AJAX commands will add the required jQuery internally and will prepare the action without us having to add the necessary JavaScript code directly.\n7.2- Rendering elements with #states property The #states property is available for use within Drupal\u0026rsquo;s render arrays and assigned to a form element, it allows you to add certain conditions to the behavior of that element, enabling changes dynamically.\nActually, the #states property ends up being managed from the JavaScript library drupal.states available for loading as a dependency in the form core/drupal.states, which points to the path where the library /core/misc/states.js is located inside Drupal, although it\u0026rsquo;s not necessary to make an explicit load of it since the rendering system that manages the Render Arrays checks the existence of the property and if it is present, it directly assigns the JavaScript library.\nThe use of this property allows the creation of elements within a form that can alter their status -show, hide, disable, enable, etc.- based on conditions both of the element itself and of another element different from the form (that when one is clicked another is hidden, for example) and using jQuery syntax when declaring the selectors.\nThe mechanics is that we will declare actions from our side and Drupal from its side will provide all the JavaScript/JQuery needed to make those declared actions happen on the fly. Everything starts with the use of #states as a property when declaring the element of the form, and from there Drupal is in charge of adding the necessary JavaScript to change elements through the drupal_process_states function which is deprecated from Drupal 8.8 and becomes part of the FormHelper class (although it maintains the same functionality).\n// States that you can apply with remote conditions (origin): empty, filled, checked, unchecked, expanded, collapsed, value. // States that you can apply over an element (target): enabled, disabled, required, optional, visible, invisible, checked, unchecked, expanded, collapsed. The basic structure of a state is that of a multidimensional array with the following form:\n[ STATE1 =\u0026gt; CONDITIONS_ARRAY1, STATE2 =\u0026gt; CONDITIONS_ARRAY2, ... ] Where an array of conditions, in turn, is another array that stores the conditions foreseen for the change of state of that element, through the scheme of use of conditions in #states:\n'#states' =\u0026gt; [ 'STATE' =\u0026gt; [ JQUERY_SELECTOR =\u0026gt; REMOTE_CONDITIONS, JQUERY_SELECTOR =\u0026gt; REMOTE_CONDITIONS, JQUERY_SELECTOR =\u0026gt; REMOTE_CONDITIONS, JQUERY_SELECTOR =\u0026gt; REMOTE_CONDITIONS, ... ], )], I the next block code we will see an example of using #states. In the context of a Form created with the Drupal Form API, we make a textfield called \u0026ldquo;Name\u0026rdquo;, reacting to the state change of a previous checkbox option. If the previous checkbox is clicked, then we make our field invisible:\n$form['name'] = [ '#type' =\u0026gt; 'textfield', '#title' =\u0026gt; t('Name:'), '#weight' =\u0026gt; 1, '#states' =\u0026gt; [ 'invisible' =\u0026gt; [ ':input[name=\u0026quot;newcheck\u0026quot;]' =\u0026gt; ['checked' =\u0026gt;TRUE], ], ], ]; 8- Troubleshooting: Problems and solutions Now in this section we are going to compile some frequent errors related to the use of JavaScript in its different modalities (vanilla, Behaviors, AJAX) and its solutions.\n8.1- Slow execution due to wrong use of \u0026lsquo;context\u0026rsquo; In our behaviors we must send -always- the context of execution of this. It is very important in terms of performance, since it facilitates the localization of HTML selectors.\nThis can be seen with another simple example, so we can observe the importance of handling the variable \u0026ldquo;context\u0026rdquo;: as we have seen in previous sections, in this value is always stored the object or part of it that has just changed (at the beginning in the first load the complete DOM, then in successive AJAX calls will be each piece of HTML modified). Not controlling this, can make that in each execution of a behavior, a selector is searched by all the document instead of its concrete zone, what can slow down the execution of the website.\nThus, a defined behavior such as this:\n(function ($) { 'use strict'; Drupal.behaviors.usingcontext = { attach: function(context, settings) { $(\u0026quot;#unsplash\u0026quot;).append('\u0026lt;p\u0026gt;Hello world\u0026lt;/p\u0026gt;'); } }; }(jQuery)); This code will generate the next response:\nThree executions (one for each load: 1 total DOM + 2 partial AJAX). In fact, it will have a similar behavior to this one (since it will go looking for the selector throughout the document):\n(function ($) { 'use strict'; Drupal.behaviors.usingcontext = { attach: function(context, settings) { $(document).find(\u0026quot;#unsplash\u0026quot;).append('\u0026lt;p\u0026gt;Hello world\u0026lt;/p\u0026gt;'); } }; }(jQuery)); However, if we facilitate jQuery\u0026rsquo;s work in the best possible way, we will achieve a more efficient behavior:\n(function ($) { 'use strict'; Drupal.behaviors.usingcontext = { attach: function(context, settings) { $(context).find('#unsplash').append('\u0026lt;p\u0026gt;Hello world\u0026lt;/p\u0026gt;'); } }; }(jQuery)); This version only runs the .append() once, because:\n The selector is located the first time, where context = full DOM. The selector is not located again, where context = HTML AJAX piece.  And within our options we have available the use of jQuery.once() as we saw in previous sections, which has a similar operation through a random selector that we add so that it can do the internal load tracking:\n(function ($) { 'use strict'; Drupal.behaviors.usingcontext = { attach: function(context, settings) { $('#unsplash').once('cacafuti').append('\u0026lt;p\u0026gt;Hello world\u0026lt;/p\u0026gt;'); } }; }(jQuery)); If we also combine the use of jQuery.once() with our own segmentation through the \u0026ldquo;context\u0026rdquo; variable, then we obtain a more optimized execution:\n(function ($) { 'use strict';Drupal.behaviors.usingcontext = { attach: function(context, settings) { $(context).once().find('#unsplash').append('\u0026lt;p\u0026gt;Hello\u0026lt;/p\u0026gt;'); } }; Or we can use:\n$('#unsplash', context).append('\u0026lt;p\u0026gt;Hello world\u0026lt;/p\u0026gt;'); I think the important thing is that we have to learn for managing the context variable to ease the JavaScript workload ;-). Here you have a set of rendering tests about Drupal Behaviors so you can see how it works on screen:\n 8.2- Loading JavaScript out of context Another case that we have seen with some frequency when inheriting a legacy project (or a new project but without respecting the proper guidelines), is the case of loads of JavaScript libraries destined only to a specific page throughout the entire website (this happens more than we think).\nSomeone went through the project, received the task, googled it, solved the task as well as they could, and then the next person arrived\u0026hellip; so, when you open the browser console, everything is a sea of warnings and red errors alerting you to JavaScript loads that cannot be done, dependencies that cannot be solved, or selectors that do not locate the elements they should. It\u0026rsquo;s time to locate the imports of our resources: what are the custom JavaScript libraries used in the project, where are they being registered and how are they being added.\nThis is where your ability to use your working IDE\u0026rsquo;s search engines to locate behaviors through the console comes into play, looking for:\n Which ones are being created. Which ones are being executed at that moment.  You will discover some libraries that have been added to the Theme in general and that should really only be added by #attached to only one specific page, for example.\n8.3- Error: illegal choice in dynamic select This is a typical error in custom forms created with the Drupal Form API when using AJAX, very common in scenarios where we want to create dynamic selects: we have an initial select and based on the choice made in this, we modify the options of the second select through a Callback.\nIt\u0026rsquo;s a classic error and very specific if you are making these fields as required fields in your form. The form validation function (even if you are overwriting your own) is re-checking the status of the form values and detecting inconsistencies. Just when we think everything is ok, we load the page, start testing and receive the following message by browser:\nOk, What\u0026rsquo;s going on? Basically, and in a very short way: Drupal is taking care of protecting your installation by preventing a form element from being completely replaced by a new one or directly added to the form definition outside the main function public function buildForm(array $form, FormStateInterface $form_state) in your form definition to avoid attacks and injections. Due to this, you have to change the implementation. Let\u0026rsquo;s see.\nThink about in what I\u0026rsquo;m doing in this piece of code from a callback function:\n /** * Callback function ready to get offices renewing the HTML component. */ public function loadRelatedOfficesCallback(array \u0026amp;$element, FormStateInterface $form_state){ // Gets the initial values from the triggered element. $triggeringElement = $form_state-\u0026gt;getTriggeringElement(); // Gets the value from the triggered element. $value = $triggeringElement['#value']; // Gets taxonomy terms children by its parent using tid. $offices = $this-\u0026gt;getOfficesbyRegion($value); // Gets the css selector to modify when callback ends. $wrapper_id = $triggeringElement[\u0026quot;#ajax\u0026quot;][\u0026quot;wrapper\u0026quot;]; // Builds a new updated version of the form component. $element = [ '#type' =\u0026gt; 'select', '#name' =\u0026gt; 'main_office', '#title' =\u0026gt; $this-\u0026gt;t('Select Office'), '#description' =\u0026gt; $this-\u0026gt;t('This will be your selected office.'), '#options' =\u0026gt; $offices, '#weight' =\u0026gt; '3', '#prefix' =\u0026gt; '\u0026lt;div id=\u0026quot;contact_form_office\u0026quot;\u0026gt;', '#sufix' =\u0026gt; '\u0026lt;/div\u0026gt;', ]; // Asks to the render service to convert the component in HTML pure. $renderer = \\Drupal::service('renderer'); $renderedField = $renderer-\u0026gt;render($element); // Creates a new AjaxResponse and adds a jQuery Command for replace the item. $response = new AjaxResponse(); $response-\u0026gt;addCommand(new ReplaceCommand('#' . $wrapper_id, $renderedField)); return $response; } Ok, but the former block doesn\u0026rsquo;t like to Drupal. It\u0026rsquo;s illegal when we\u0026rsquo;re 1) creating a new element. 2) Ask to the render servie to transform the element in HTML and 3) Loading the new element in an existing wrapper using AJAX Commands. This is problematic and It\u0026rsquo;s an approach that we should avoid.\nWhat can we do? We can think about two options: one more secure than other. Let\u0026rsquo;s see.\n  Mark the element to be replaced as validate using the property #validated' =\u0026gt; TRUE, avoiding that Drupal reviewed this and let your change pass. Less secure.\n  Change the focus: do not perform the replacement of the entire element on HTML, but dynamically modify the $options value array through Callback. More secure and recommended.\n  See this related proposal: Suppress validation of required fields on AJAX calls in Drupal 9.x\n9- Links and Reading resources In this section you will find links to guides, relevant information and related reading resources.\n9.1- JavaScript fundamentals  Understanding Scope and Context in JavaScript Objects and properties in JavaScript Web Storage API developer.mozilla.org/Web_Storage_API  9.2- Functions in JavaScript and the IIFE format  https://developer.mozilla.org/en-US/docs/Glossary/IIFE https://developer.mozilla.org/en-US/docs/Web/JavaScript/A_re-introduction_to_JavaScript#Functions https://medium.com/@vvkchandra/essential-javascript-mastering-immediately-invoked-function-expressions-67791338ddc6  9.3- JavaScript and Drupal  Unofficial JavaScript API for Drupal, by Théodore Biadala, @nod_ https://stackoverflow.com/questions/34025396/how-to-open-a-modal-in-drupal-8-without-using-a-link An example about Drupal.dialog https://stackoverflow.com/questions/3941426/drupal-behaviors https://sqndr.github.io/d8-theming-guide/javascript/behaviors.html http://www.jaypan.com/tutorial/high-performance-javascript-using-drupal-7s-javascript-api Drupal.org guide: Creating Custom Module How To Develop a Drupal 9 Website on Your Local Machine Using Docker and DDEV Drupal org Docs: Libraries, options and details public function HtmlResponseAttachmentsProcessor. Drupal Fast Tips: The magic of \u0026lsquo;attached\u0026rsquo; Erasing traces of generator in Drupal projects api.drupal.org/core.services.yml/current_user The Drupal.t function The Drupal.dialog API The Drupal.theme function. AJAX API in Drupal. AjaxResponse Class in Drupal AJAX API AJAX Callback Commands in Drupal drupal_process_states function in Drupal 8.x FormHelper class in Drupal 9.x Suppress validation of required fields on AJAX calls in Drupal 9.x  9.4- jQuery  jQuery API documentation jQuery release history jQuery API: Selectors jQuery Standars (old) jQuery Visualization of use in Drupal 8 jQuery getJSON for AJAX jQuery .slideDown() function Is jQuery.once() needed if we filter by context? https://github.com/robloach/jquery-once https://github.com/RobLoach/jquery-once/blob/master/API.md#readme  9.5- Snippets  Drupal 8 || 9: Creating custom module using Drush generate Drupal 8 || 9: Altering HTML in headers from hooks Drupal 8 || 9: Ultra-lightweight deploy of Drupal setup (without Apache or MySQL) Drupal 8 || 9: Altering HTML in headers from hooks Drupal 8 || 9: Deploying a new Drupal Site with Composer / Drush on the fly Drupal 8 || 9: Creating modules and forms using Drupal Console Drupal 9 in six steps using DDEV: Quick Deploy  9.6- Others  Web Storage Support Test. Bacon Ipsum, a lorem ipsum generator with API.  10- :wq! If you have managed to reach the end of this guide linearly, congratulations! Thanks for your patience and I really hope it has been useful to you.\nThis guide has been published without -direct- profit, but my personal interest is that it spreads and helps my communication. If it has been useful to you, share it using the \u0026ldquo;share\u0026rdquo; of this site, putting a simple tweet. It will be important for me. Thank you.\nRecommended song   ","permalink":"https://www.therussianlullaby.dev/blog/guide-how-to-integrate-javascript-in-drupal-8-9/","tags":["Drupal Behaviors","Drupal Development","Backend","PHP"],"title":"Guide: How to integrate JavaScript in Drupal 8-9"},{"categories":["Reading"],"contents":"For a long time I wondered whether I should write a review of Drupal 8 Module Development, the previous edition of the current book. For me there were two important factors: on the one hand, it was a very ambitious book in scope and content, so writing a concise review was a real challenge. On the other hand, Drupal 9 was already on its way, and the book could quickly become outdated. Luckily, Drupal 9 has already arrived, and the transition from 8 to 9 has not been traumatic at all (there are still many people trapped in the crack between Drupal 7 and Drupal 8), but rather a fairly smooth road. There are no deep differences between Drupal 8 and Drupal 9 in how we approach custom module development, and with this new Drupal 9 edition, everything is ready for a review of this ambitious book. There we go!\n Picture from Unsplash, user Erol Ahmed, @erol\n Table of Contents\n1- Introduction 2- The Book 3- Recommendations 4- Book Information 5- Fast Review 6- Ratings 7- :wq!\n 1- Introduction Daniel Sipos (or just Danny Sipos, or Upchuk on Drupal.org, or @drupalexp on Twitter) has been a Drupal developer for many years. He is also a regular speaker at Drupal events and the person behind Web Omelette (https://www.webomelette.com), a popular site you should know if you are a Drupal developer (put the URL in your favorites if you don\u0026rsquo;t know it yet). I already knew his website from looking for doubts, consulting references and obtaining examples (almost as a study and training online resource), when I had the opportunity to be a little closer to his work through a project in the context of the European Commission, where I had to use a Drupal distribution and set of tools and resources, called Open Europa to generate new platforms for the European Commission. At that time I left some written lines here in my notebook about this.\nAs it is easy to contextualize, the author has a lot of experience in the field of knowledge about Drupal, and that shows well. Perhaps that is why he has written successive editions (Drupal 9 would be the third iteration) of what can perhaps be considered the fundamental book for Drupal-based development. In fact, because of its scope and size, this book should be considered the Holy Bible of Drupal. A resource that should be part of any developer\u0026rsquo;s shelf or ebook/kindle/tablet. But if the metaphor about the bible were accurate, then we could extend it a little further. Because basically, a sacred text requires its own \u0026ldquo;hermeneutics\u0026rdquo; to be interpreted correctly, that is, its own study of meaning. We hope that this article will be useful to unravel its essence and value. Let\u0026rsquo;s continue.\n2- The Book Imagine a tutorial that contains all the most important issues of Drupal. A review through its diverse concepts, systems, subsystems, elements\u0026hellip;Well here you have about six hundred pages with all of it. As we said in the previous section by way of introduction, the true Drupal bible. Here you will have access to all the ins and outs of Drupal, even those parts that are not well known even to many senior developers (such as the TypedData API). The paradox, the real tension of this book is to assume a length and depth that sometimes does not meet the expectations of didactics for people who want to start in Drupal. In my experience, I have tried to share the book with fellow juniors and it has been difficult for them to advance: first those who are not fluent in English, but then those who were. In all the cases observed, the colleagues considered the book too hard, rough, complex\u0026hellip; however, when they commented and shared it with the older profiles, they made a much more agile and profitable use of the book. They did know how to get into the inner workings of the book. This is important and marks my view of the book in some ways.\nBased on these observations, I have concluded that if Drupal were a medicine, I would say that this extensive manual is something like a help for healthy people. It is in itself a treatment for people who are already initiated, those who are already able to place themselves within the world of Drupal. It is really curious, since it would be easy to accuse or blame the book of anti - didactic (in the second chapter, right when you create your first Drupal module the content are already talking about services or Form API, this is a very, very fast trip). You could say that everything (or almost everything) is sacrificed to try to make the ambitious goal of explaining Drupal deeply (or to demonstrate the author\u0026rsquo;s mastery of the subject, but that belongs to another set of interpretations). This book contains the same information as a thousand articles, hundreds of posts, and dozens of online guides. It is the perfect accumulation of Drupal content, and in order not to get lost in the way, it is better to be in the hands of a senior profile that acts as a sherpa.\nThe book -by the way- is still edition after edition the reference guide on Drupal, as you can see in the books section of the Drupal.org website: https://www.drupal.org/books.\nSome APIs that are discussed in this book:\n Mail API: Send emails programmatically, by code (Chapter 3: Logging and Mailing). Token API: How to manage formatted placeholders (Chapter 3: Logging and Mailing). State API: Simple Storage by key/value pairs (Chapter 6: Data Modeling and Storage). UserData API: Storage of some pieces of data related to users (Chapter 6: Data Modeling and Storage). Configuration API: Related with this important subsystem of Drupal, set of methods (Chapter 6: Data Modeling and Storage). TypedData API: Low level object oriented API for descriptive data processing over PHP (Chapter 6: Data Modeling and Storage). Entity API: The most important API for interactions with Entities (Chapter 6: Data Modeling and Storage). Database API: Describes methods and resources for managing the database, including queries (Chapter 8: The Database API). Schema API: Allows defining database table structures (Chapter 8: The Database API). Cache API: Creating, Reading and Invalidating caching entries (Chapter 11: Caching). AJAX API: Client-Side interactions using PHP and without JavaScript (Chapter 12: JavaScript and the AJAX API). Translation API: Working with entity translations (Chapter 13: Internationalization and Languages). Lock API: Low level resource for processes (Chapter 14: Batches, Queues, and Cron).  3- Recommendations Mainly, this book is for someone who wants to deepen their knowledge of Drupal.\nIn principle, there are some requirements to enjoy this book, related to some kind of previous experience in the Drupal World, or it runs the risk of becoming a \u0026ldquo;book - door\u0026rdquo;, since you will have to go to the Internet to find many concepts and other examples to establish the knowledge set out in this great manual. On the other hand, we will say: Ok, the book it\u0026rsquo;s a MUST. All teams should have a copy nearby, for consultations, to resolve doubts, to clarify ideas. So if you\u0026rsquo;ve decided that you want to make Drupal the best way to make a living (and earn your salary). I would recommend you take this tutorial and handle it as what it is: an excellent cartographic compendium, with plenty of maps to go through all the routes of Drupal.\nRead it, study it, practice it and repeat all the examples. It\u0026rsquo;s a master\u0026rsquo;s degree in Drupal whose execution only depends on you (and with a very good price).\n4- Book Information    Field Description     Title Drupal 9 Module Development.   Author Daniel Sipos.   Publisher Packt.   Date August, 2020.   Pages 626   Overview Comprehensive guide to Drupal 9 development.   Keywords Drupal, Backend, API, PHP, Symfony, Testing.   Price 30$ // 29.99€ (aprox)   Links Packt, Amazon    5- Fast Review    Question // Answer     1- Is this book progressive, Iterative and Incremental?   No, the book tries to be lineal using the idea of implementing a custom module, but it\u0026rsquo;s more complex than this.   2- Does it offer specific solutions to particular problems or concrete issues?   Yes, the book is full-filled with a lot of proposals, solutions and ideas.   3- Does it explain well the original problems or needs it aims to solve?   Yes, Yes, although it does not have a problem-solution structure it\u0026rsquo;s rather a topic-exposition.   4- Is this book rich in examples?   Yes, it contains many examples and practical demonstrations.   5- Is this book written in plain English, suitable for non-English speakers?   No, it contains set phrases, expressions, usages that sometimes take it away from plain English. .   6- Is it up to date?   Yes, is a recent issue from August 2019.    6- Ratings 7- :wq! Recommended song: Dexter Gordon - Tanya   ","permalink":"https://www.therussianlullaby.dev/blog/books-drupal-module-development/","tags":["Books","Drupal Development","Backend","PHP","Drupal Plugins"],"title":"Books/ Drupal 9 Module Development"},{"categories":["Migrations"],"contents":"Following the previous post, in this article I wanted to share again some guidelines for debugging Drupal Migrations. While in the last article I wrote down some questions about modules, plugins and configuration object management, in this case I felt like stopping at something more \u0026ldquo;code-oriented\u0026rdquo;, more specific. I have chosen for this article some guidelines to configure Xdebug in an IDE (in this case I have used PhpStorm) for migrations running in a locally deployed Drupal using DDEV. With the aim of debugging a Migration process -although actually the process is similar for any other development, since it is something certainly transversal-, here I share with you my fifth article about migrations at Drupal. I hope you find it useful.\n Picture from Unsplash, user Neenu Vimalkumar, @dreamquest12610\n Table of Contents\n1- Introduction to Xdebug 2- Xdebug for DDEV 3- Steps for setting up the IDE 4- Errors launching Drush in containers 5- :wq!\n This article is part of a series of posts about Drupal Migrations:\n1- Drupal Migrations (I): Basic Resources\n Core Modules for Migrations in Drupal. Contributed Modules for Migrations in Drupal. Contributed Modules for Plugins in Migrations. Contributed Modules Drush - Related. Authors you should know.  2- Drupal Migrations (II): Examples\n Introduction: Migration as code or as configuration. Arrangements: Migrating embedded data and CSV file. Approaches for the Migrations. Executing Migrations. Key Concepts for the Migrations. Resources for the Migrations.  3- Drupal Migrations (III): Migrating from Google Spreadsheet\n Remember ETL processes. Exposing Data Through Google Spreadsheet. Special Properties from the JSON transformation. Characteristics of the Migration. Custom Module for Migration.  4- Drupal Migrations (IV): Debugging Migrations First Part\n Basic Debugging of a Drupal Migration: files, database tables and configuration objects. Average Debugging with Migrate Devel Module.  5- Drupal Migrations (V): Debugging Migrations-II\n Introduction to Xdebug. Xdebug for DDEV. Steps for setting up the IDE. Errors launching Drush in containers.   1- Introduction to Xdebug In this section we\u0026rsquo;re going to share some debugging tactics for our migrations that can complement the actions discussed in the previous article: we already know how to check the configuration files, how to use certain processing plugins to extract information from the process and at this moment we\u0026rsquo;re going to add the third piece to the construction: how to debug the operation of a migration from the point of view of the related code, that is, through the PHP classes involved in all the mechanics.\nXdebug is a classic PHP extension that in its simplest dimension, lets us track variables from the code to see their values through the execution flows of your software. In this post we won\u0026rsquo;t stop to explain how to install, configure and manage Xdebug in general terms, but we can be interested in a particular case: to run your local Drupal installations and test your migrations, you can use DDEV, a tool for creating local development environments based on Docker containers that already has a pre-installation of Xdebug ready to use.\nDDEV avoids having to implement classic LAMP environments by compartmentalizing networks of Docker containers already pre-configured with all the development tools already included, including Xdebug. Here you can find more information, articles and resources about the tool: https://github.com/drud/awesome-ddev.\n2- Xdebug for DDEV Accepting the premise that we are in an environment built with DDEV, the activation of Xdebug can easily be done in two ways:\nEither by editing the config.yaml file in the .ddev resource folder, path: your_project/.ddev/config.yaml, where you can activate xdebug on line 9 of the file:\nOr by command line, using the DDEV-related commands outside the container:\nddev exec enable_xdebug ddev exec disable_xdebug Or also:\nddev xdebug ddev xdebug on ddev xdebug off The above commands will serve to activate and deactivate the resource (for performance reasons, the initial state of Xdebug in a DDEV installation is deactivated by default). Since Xdebug is a server-side tool, it is already in the container acting as a web server and requires no further configuration. It\u0026rsquo;s just a matter of preparing our usual IDE.\nIn essence, any IDE works the same way with Xdebug: it listens on a port and reacts as soon as it receives an input. Xdebug usually uses port 9000, and for each IDE there are some configuration examples that you can follow here:\n ddev.readthedocs.io/#step-debugging-with-ddev-and-xdebug . glamanate.com/xdebug-over-command-line-ddev.  And the basic technique was already exposed at the time by the PhpStorm team (Now I\u0026rsquo;m using PhpStorm for this example): https://blog.jetbrains.com/phpstorm/2012/03/new-in-4-0-easier-debugging-of-remote-php-command-line-scripts/.\n3- Steps for setting up the IDE Let\u0026rsquo;s look at some basic steps of setting up Xdebug in the PhpStorm IDE, although the philosophy behind it in others like VSCode is just the same. The first idea is that by activating Xdebug, we\u0026rsquo;ll have it available for any PHP script running from the command line. This makes it especially valuable for debugging Drush or Drupal Console (Old and abandoned Drupal Console, BTW) . In this case, we also add the enormous facilitation of automated activation already in Xdebug in local DDEV-based deployments, so we add a lot of facilities to debug our migration.\nLet\u0026rsquo;s take the next steps:\n1- Server for the project Open the Server configuration for your project: go to File-\u0026gt; Settings -\u0026gt; Languages \u0026amp; Frameworks -\u0026gt; PHP -\u0026gt; Servers and create a new Server in the side menu. Give it a name (it will be easier if you use the name of your DDEV deployment for the project). Add port 80 and activate Xdebug as the debugger.\n2- Initial setup When using PhpStorm non-locally, we need to set up an \u0026ldquo;external\u0026rdquo; server and map the project files. In the case of not being in a classic LAMP environment (that would be local environment), we will need to make this configuration (Docker or DDEV suppose a non-local way of deployment in some way). DDEV serves the Drupal installation files from within its web container, starting with /var/www/html/ but the files are located locally within a typical /home/user/folder/projects/my_project path, so we will load this file mapping.\n3- Listening for debug connections Activate the active listening button for debugging.\n4- Load environment variable in the container For this we have two options. We can either load it for each debugging session, entering it into the DDEV container using the ddev ssh instruction and then loading the environment variable from inside the web container with the next instruction, just execute: export PHP_IDE_CONFIG=\u0026quot;serverName=your.project.name.ddev.site\u0026quot;\nYou can also permanently load the environment variable by creating a companion file in the .ddev/ folder of the project, a docker-compose.env.yaml file that is located next to the other docker-compose related files from the DDEV configuration, with the content:\nversion: '3.6' services: web: environment: - PHP_IDE_CONFIG=serverName=your.project.name.ddev.site This file will be merged with the other files when you restart the project container network (ddev start / ddev stop) and the variable will be loaded more persistently and automatically.\n5- Breakpoints We can then try to place various breakpoints along the code involved in a migration. For example, for processing that does not require transformation treatment (only the transfer from a source to a destination), we already know that a Process Plugin provided by default by the Migrate API is used: the Get.php class, present in /core/modules/migrate/src/Plugin/migrate/process/Get.php and in this case we can insert some breakpoints in what will be a safe place for certain migration examples: for example in the transform() method of the class (line 106):\n4- Errors launching Drush in containers When we have the IDE in active listening for debugging, some breakpoints strategically located and we launch some Drush command in the context of DDEV, it is possible that when executing Drush commands, from the IDE we receive an error about the mapping of files associated to Drush. Without meaning to, it is executed on the global Drush launcher of the container, installed in the path /usr/local/bin/drush (of the operating system of the web container).\nIn that case, we have two options:\nỲou can run drush directly on the /vendor address of the project (which is mapped in the previous configuration of our remote server), using the forms:\n From outside the container: ddev exec /vendor/bin/drush migrate-status From inside the container (ddev ssh): /vendor/bin/drush migrate-status  Another option is recommended by Randy Fay, @randyfay (one of the creators of DDEV) in the comment on this GitHub issue: github.com/ddev/issues/2341#issuecomment-650618158.\nThat is, take advantage of the Drush command example as usually described in the drush.example file in the path /your-name-project/.ddev/commands/web/ and modify the Drush use case it contains by directly placing the previous path to the project vendor, going from : drush $@  to: /var/www/html/vendor/bin/drush $@\nThis will allow us to run debugging with Xdebug for our migrations in DDEV environments. Here you can find more information about it:\n ddev.io/#phpstorm-debugging-setup. github.com/docker4drupal/issues/127. github.com/ddev/issues/2341.  Now you can run your migration safely and comfortably! You can now launch your migration-related Drush commands, get the expected feedback from your breakpoints, and start debugging your migration.\n5- :wq! Recommended song   ","permalink":"https://www.therussianlullaby.dev/blog/drupal-migrations-five-debugging-migrations-ii/","tags":["Drupal Migrations","Migrate API","Drupal Development","Drupal Plugins","ETL"],"title":"Drupal Migrations (V): Debugging Migrations-II"},{"categories":["Migrations"],"contents":"The Drupal migrations, despite their linearity in terms of definitions, contain a lot of inherited complexity. The reason is very intuitive: although the Migrate API is a supersystem that offers a very simple \u0026ldquo;interface\u0026rdquo; of interactions for the user-developer who wants to build migration processes, in reality several subsystems work by interacting with each other throughout a migration process: Entities, Database, Plugins\u0026hellip;There are a lot of classes involved in even the simplest migration process. If we add the irrefutable fact that a migration will tend to generate errors in many cases until it has been refined, it\u0026rsquo;s clear then that one of our first needs will be to learn\u0026hellip;how to debug migrations.\n Picture from Unsplash, user Krzysztof Niewolny, @epan5\n Table of Contents\n1- Introduction 2- Basic Debugging: Keep an eye on your file 3- Average Debugging with Migrate Devel 4- :wq!\n This article is part of a series of posts about Drupal Migrations:\n1- Drupal Migrations (I): Basic Resources\n Core Modules for Migrations in Drupal. Contributed Modules for Migrations in Drupal. Contributed Modules for Plugins in Migrations. Contributed Modules Drush - Related. Authors you should know.  2- Drupal Migrations (II): Examples\n Introduction: Migration as code or as configuration. Arrangements: Migrating embedded data and CSV file. Approaches for the Migrations. Executing Migrations. Key Concepts for the Migrations. Resources for the Migrations.  3- Drupal Migrations (III): Migrating from Google Spreadsheet\n Remember ETL processes. Exposing Data Through Google Spreadsheet. Special Properties from the JSON transformation. Characteristics of the Migration. Custom Module for Migration.  4- Drupal Migrations (IV): Debugging Migrations First Part\n Basic Debugging of a Drupal Migration: files, database tables and configuration objects. Average Debugging with Migrate Devel Module.  5- Drupal Migrations (V): Debugging Migrations-II\n Introduction to Xdebug. Xdebug for DDEV. Steps for setting up the IDE. Errors launching Drush in containers.   1- Introduction In the wake of the latest articles, I wanted to continue expanding information about migration in Drupal. I was thinking about writing a sub-series of debugging migrations (inside the main series about Drupal Migrations), and I want to publish now the first part, just a set of basic steps to get all the available information from a migration process. All the examples in this post were taken of the migration_google_sheet example, from my Gitlab account.\n2- Basic Debugging (Keep an eye on your files) First, we will start with a very primary approach to error detection during a migration. To begin with, it is essential to keep the focus on reducing the range of error possibilities as much as possible by approaching the migration in an iterative and incremental manner. In other words: we will go step by step and expand our migrated data.\n2.1- Reviewing your Migration description file First of all we are going to comment on the most intuitive step of all we will take, since sometimes there are errors that occur at first sight and not because they are recurrent but end up being more obvious.\nThe first steps in our process of debugging a migration will be a review of two fundamental issues that usually appear in many migrations. So before anything else, we\u0026rsquo;ll do a quick review of:\nWhitespaces: Any extra whitespace may be causing us problems at the level of the migration description file: we review all lines of the file in a quick scan to detect extra whitespace.\nErrors in the indentation: The migration description file has a format based on YAML, a language for data serialization based on a key scheme: a value where it is structured by parent - child levels and an indentation of two spaces to the right at each level down in the hierarchy. It is very frequent that some indentation is not the right one and this ends up producing an error in the processing of the file. As a measure, as in the previous case, we will review all the cases of indentations registered in the file.\nYou can rely on a YAML syntax review service such as www.yamllint.com, but you will have to monitor the result as well.\n2.2- Reviewing registers in database If you\u0026rsquo;re in a basic Drupal installation (standard profile) we have seventy-three tables, after the activation of the basic modules related to migrations: migrate, migrate_plus, migrate_tools and in this case the custom migration_google_sheet_wrong the number of tables in the database is seventy-five. Two more tables have been generated:\ncache_migrate cache_discovery_migration But also, later, after executing the migration with ID taxonomy_google_sheet_wrong contained in our custom module, we see in the database that two new tables have been generated related to the executed migration:\n  migrate_map_taxonomy_google_sheet This table contains the information related to the movements of a row of data (migrations are operated \u0026lsquo;row\u0026rsquo; to \u0026lsquo;row\u0026rsquo;). Migrate API is in charge of storing in this table the source ID, the destination ID and a hash related to the \u0026lsquo;row\u0026rsquo; in this data mapping table. Combinations between the source ID and the hash of the row operation then make it easier to track changes, progressively update information, and cross dependencies when performing a batch migration (see below for how they are articulated). The lookup processes for migrations are supported by this data: for example, to load a taxonomy term you must first lookup its \u0026ldquo;parent\u0026rdquo; term to maintain the hierarchy of terms. If we go to our database and we do not see recorded results after launching a migration, no data was stored and the migration requires debugging.\n  migrate_message_taxonomy_google_sheet In this table, messages associated to the executed migration will be stored, structured in the same way as the previous table (based on the processing of a \u0026lsquo;row\u0026rsquo; of the migration), each message with its own identifier and an association to the id_hash of the \u0026lsquo;row\u0026rsquo; of origin of the data:\n  This information can be obtained through Drush, since the content of this table is what is shown on the screen when we execute the instruction:\ndrush migrate:messages id-migration And this can be a useful way to start getting information about the errors that occurred during our migration.\n2.4- Reloading Configuration objects Another issue we\u0026rsquo;ll need to address while debugging our migration is how to make it easier to update the changes made to the configuration object created from the migration description file included in the config/install path.\nAs we mentioned earlier, each time the module is installed a configuration object is generated that is available in our Drupal installation. In the middle of debugging, we\u0026rsquo;ll need to modify the file and reload it to check that our changes have been executed. How can we make this easier? Let\u0026rsquo;s take a look at some guidelines.\nOn the one hand, we must associate the life cycle of our migration-type configuration object with the installation of our module. For it, as we noted in the section 2.3.2- Migration factors as configuration, we will declare as forced dependency our own custom module of the migration:\ndependencies: enforced: module: - migration_google_sheet We can use both Drush and Drupal Console to perform specific imports of configuration files, taking advantage of the single import options of both tools:\nUsing Drupal Console\ndrupal config:import:single --directory=\u0026#34;/modules/custom/migration_google_sheet/config/install\u0026#34; --file=\u0026#34;migrate_plus.migration.taxonomy_google_sheet.yml\u0026#34; Using Drush\ndrush cim --partial --source=/folder/ Similarly, we can also remove active configuration objects using either Drush or Drupal Console:\ndrush config-delete \u0026#34;migrate_plus.migration.taxonomy_google_sheet\u0026#34; drupal config:delete active \u0026#34;migrate_plus.migration.taxonomy_google_sheet\u0026#34; If we prefer to use the Drupal user interface, there are options such as the contributed Config Delete module drupal.org/config_delete , which activates extra options to the internal configuration synchronization menu to allow the deletion of configuration items from our Drupal installation. It\u0026rsquo;s enough to download it through Composer and enable it through Drush or Drupal Console:\ncomposer require drupal/config_delete drush en config_delete -y This way we can re-import configuration objects without colliding with existing versions in the database. If you choose to update and compare versions of your configuration, then maybe the Configuration Update Manager contributed module can be a good option https://www.drupal.org/project/config_update.\n3- Average Debugging with Migrate Devel Well, we have looked closely at the data as we saw in the previous section and yet our migration of taxonomy terms from a Google Spreadsheet seems not to work.\nWe have to resort to intermediate techniques to obtain more information about the process. In this phase our allies will be some modules and plugins that can help us to better visualize the migration process.\n3.1- Migrate Devel Migrate Devel https://www.drupal.org/project/migrate_devel is a contributed module that brings some extra functionality to the migration processes from new options for drush. This module works with migrate_tools and migrate_run.\nUPDATE (03/07/2020): Version 8.x-2.0-alpha2 Just as I published this article, Andrew Macpherson (new maintainer of the Migrate Devel module and one of the accessibility maintainers for Drupal Core), left a comment that you can see at the bottom of this post with some important news. Well, since I started the first draft of this article, a new version had been published, released on June 28th and it\u0026rsquo;s already compatible with Drush 9 (and I didn\u0026rsquo;t know\u0026hellip;) So now you know there\u0026rsquo;s a new version available to download compatible with Drush 9 and which avoids having to install the patch exposed below.\nTo install and enable the module, we proceed to download it through composer and activate it with drush: Migrate Devel 8.x-2.0-alpha2.\ncomposer require drupal/migrate_devel # To install the 2.0 branch of the module: composer require drupal/migrate_devel:^2.0 drush en migrate_devel -y Follow for versions prior to 8.x-2.0-alpha2: If you\u0026rsquo;re working with versions prior to 8.x-2.0-alpha2, then you have to know some particularities: The first point is that it\u0026rsquo;s was optimized for a previous version of Drush (8) and it does not seem to have closed its portability to Drush 9 and higher.\nThere\u0026rsquo;s a tag 8.x.1.4 from two weeks ago in the 8.x-1.x branch: migrate_devel/tree/8.x-1.4\nThere is a required patch in its Issues section to use it with Drush \u0026gt; 9, and if we use this module, this patch https://www.drupal.org/node/2938677  will be almost mandatory. The patch does not seem to be in its final version either, but at least it allows controlled and efficient execution of some module features. Here we will see some of its contributions.\nAnd to apply the patch we can download it with wget and apply it with git apply:\ncd /web/modules/contrib/migrate_devel/ wget https://www.drupal.org/files/issues/2018-10-08/migrate_devel-drush9-2938677-6.patch git apply migrate_devel-drush9-2938677-6.patch Or place it directly in the patch area of our composer.json file if we have the patch management extension enabled: https://github.com/cweagans/composer-patches.\nUsing:\ncomposer require cweagans/composer-patches And place the new patch inside the \u0026ldquo;extra\u0026rdquo; section of our composer.json file:\nHow it works: The launch of a migration process with the parameters provided by Migrate Devel will generate an output of values per console that we can easily check, for example using \u0026ndash;migrate-debug:\nThis is a partial view of the processing of a single row of migrated data, showing the data source, the values associated with this row and the final destination ID, which is the identifier stored in the migration mapping table for process tracking:\nNow we can see in the record that for the value 1 in origin (first array of values), the identifier 117 was assigned for the load in destination. This identifier will also be the internal id of the new entity (in this case taxonomy term) created within Drupal as a result of the migration. This way you can relate the id of the migration with the new entity created and stored.\nWhat about event subscribers?, Migrate Devel creates an event subscriber, a class that implements EventSubscriberInterface and keeps listening to events generated from the event system of the Drupal\u0026rsquo;s Migrate API, present in the migrate module of Drupal\u0026rsquo;s core:\nCalled from +56 /var/www/html/web/modules/contrib/migrate_devel/src/EventSubscriber/MigrationEventSubscriber.php The call is made from the class where events are heard and actions from the module\u0026rsquo;s Event classes are read. Many events are defined there modules/migrate/src/Event, but in particular, two that are listened to by Migrate Devel:\n MigratePostRowSaveEvent.php MigratePreRowSaveEvent.php  What are the two Drush options offered by Migrate Devel, and in both cases results in a call to the Kint library dump() function provided by the Devel module to print messages. In fact the call to Kint has changed in the last version 8.x-2.0-alpha2, where Kint is replaced by a series of calls to the Dump method of ths Symfony VarDumper. Where we used to do:\n /** * Pre Row Save Function for --migrate-debug-pre. * * @param \\Drupal\\migrate\\Event\\MigratePreRowSaveEvent $event * Pre-Row-Save Migrate Event. */ public function debugRowPreSave(MigratePreRowSaveEvent $event) { $row = $event-\u0026gt;getRow(); $using_drush = function_exists('drush_get_option'); if ($using_drush \u0026amp;\u0026amp; drush_get_option('migrate-debug-pre')) { // Start with capital letter for variables since this is actually a label. $Source = $row-\u0026gt;getSource(); $Destination = $row-\u0026gt;getDestination(); // We use kint directly here since we want to support variable naming. kint_require(); \\Kint::dump($Source, $Destination); } } Now we\u0026rsquo;re doing:\n /** * Pre Row Save Function for --migrate-debug-pre. * * @param \\Drupal\\migrate\\Event\\MigratePreRowSaveEvent $event * Pre-Row-Save Migrate Event. */ public function debugRowPreSave(MigratePreRowSaveEvent $event) { if (PHP_SAPI !== 'cli') { return; } $row = $event-\u0026gt;getRow(); if (in_array('migrate-debug-pre', \\Drush\\Drush::config()-\u0026gt;get('runtime.options'))) { // Start with capital letter for variables since this is actually a label. $Source = $row-\u0026gt;getSource(); $Destination = $row-\u0026gt;getDestination(); // Uses Symfony VarDumper. // @todo Explore advanced usage of CLI dumper class for nicer output. // https://www.drupal.org/project/migrate_devel/issues/3151276 dump( '---------------------------------------------------------------------', '| $Source |', '---------------------------------------------------------------------', $Source, '---------------------------------------------------------------------', '| $Destination |', '---------------------------------------------------------------------', $Destination ); } } You can see the update and changes in migrate_devel/8.x-2.0-alpha2/src/EventSubscriber/MigrationEventSubscriber.php.\nAnd you can get more information about creating events and event subscribers in Drupal here in The Russian Lullaby: Building Symfony events for Drupal.\n3.2- Debug Process Plugin The contributed module Migrate Devel also brings a new processing plugin called \u0026ldquo;debug\u0026rdquo; and defined in the Debug.php class. This PHP class can be found in the module path: /web/modules/contrib/migrate_devel/src/Plugin/migrate/process/Debug.php and we can check its responsibility by reading its annotation section in the class header:\n/** * Debug the process pipeline. * * Prints the input value, assuming that you are running the migration from the * command line, and sends it to the next step in the pipeline unaltered. * * Available configuration keys: * - label: (optional) a string to print before the debug output. Include any * trailing punctuation or space characters. * - multiple: (optional) set to TRUE to ask the next step in the process * pipeline to process array values individually, like the multiple_values * plugin from the Migrate Plus module. And it consists directly with the transform() method - inherited from the ProcessPluginBase abstract class, where instead of applying transformation actions during processing, it simply uses PHP\u0026rsquo;s print_r function to display information by console and will print both scalar values and value arrays.\nThis plugin can be used autonomously, being included as part of the migration pipeline, so that it prints results throughout the processing of all value rows. In this case, we are going to modify the pipeline of the processing section of our taxonomy terms migration, with the idea of reviewing the values being migrated.\nTo begin with, we are going to modify the structure. We already know (from previous chapters) that this is actually the way:\nprocess: name: name description: description path: url status: published It\u0026rsquo;s just an implicit way of using the Get.php Plugin which is equivalent to:\nprocess: name: plugin: get source: name description: plugin: get source: description path: plugin: get source: url status: plugin: get source: published Now we add to the pipeline the Debug plugin with an information label for processing:\nprocess: name: plugin: debug label: \u0026#39;Processing name field value: \u0026#39; plugin: get source: name description: plugin: debug label: \u0026#39;Processing description field value: \u0026#39; plugin: get source: description path: plugin: debug label: \u0026#39;Processing path field value: \u0026#39; plugin: get source: url status: plugin: debug label: \u0026#39;Processing status field value: \u0026#39; plugin: get source: published After this change we reload the migration configuration object by uninstalling and installing our module (as it is marked as a dependency, when uninstalled the migration configuration will be removed):\ndrush pmu migration_google_sheet \u0026amp;\u0026amp; drush en migration_google_sheet -y So when we run the migration now we will get on screen information about the values:\nThis way we get more elaborated feedback on the information to be migrated. If we want to complete this information and thinking about more advanced scenarios, we can combine various arguments and options to gather as much information as possible. Let\u0026rsquo;s think about reviewing the information related to only one element of the migration. We can run something like:\n drush migrate-import --migrate-debug taxonomy_google_sheet --limit=\u0026quot;1 items\u0026quot; Which will combine the output after storage (unlike its \u0026ndash;migrate-debug-pre option), showing in a combined way the output of the Plugin, the values via Kint and the final storage ID of the only processed entity.\nIn this case, we only see basic values and with little processing complexity (we only extract from Source and load in Destiny) but in successive migrations we will be doing more complex processing treatments and it will be an information of much more value. Interesting? think about processing treatment for data values that must be adapted (concatenated, cut, added, etc)\u0026hellip;if at each step we integrate a feedback, we can better observe the transformation sequence.\nHere you can check the Plugin code: migrate_devel/src/Plugin/migrate/process/Debug.php.\nHere you can review the Drupal.org Issue where the idea of implementing this processing Plugin originated: https://www.drupal.org/node/3021648.\nWell, with this approach to Migrations debugging we will start the series on debugging\u0026hellip;soon more experiences!\n4- :wq! Recommended song   ","permalink":"https://www.therussianlullaby.dev/blog/drupal-migrations-four-debugging-migrations-i/","tags":["Drupal Migrations","Migrate API","Contrib Modules","Drupal Plugins","ETL"],"title":"Drupal Migrations (IV): Debugging Migrations-I"},{"categories":["Migrations"],"contents":"The systems and subsystems related to Drupal\u0026rsquo;s migration API are certainly exciting. In the previous articles in this series, I wanted to draw as complete a map as possible (part one) of the vast amount of resources, possibilities and referenced experts. In the second part I wanted to expose some basic mechanics of the migration processes in Drupal and knowing that this opens the door to thousands of options, possibilities and techniques\u0026hellip;.I didn\u0026rsquo;t want to let a third article go by without sharing some experiences migrating data from a common format as a Google Spreadsheet, just an usual way in which sometimes the data are sent.\n Picture from Unsplash, user Pan Xiaozhen, @zhenhappy\n Table of Contents\n1- Introduction and remember ETL processes 2- Exposing data through Google Spreadsheet 3- Special Properties from the JSON transformation 4- Characteristics of the Migration 5- Custom Module for Migration 6- :wq!\n This article is part of a series of posts about Drupal Migrations\n1- Drupal Migrations (I): Basic Resources\n Core Modules for Migrations in Drupal. Contributed Modules for Migrations in Drupal. Contributed Modules for Plugins in Migrations. Contributed Modules Drush - Related. Authors you should know.  2- Drupal Migrations (II): Examples\n Introduction: Migration as code or as configuration. Arrangements: Migrating embedded data and CSV file. Approaches for the Migrations. Executing Migrations. Key Concepts for the Migrations. Resources for the Migrations.  3- Drupal Migrations (III): Migrating from Google Spreadsheet\n Remember ETL processes. Exposing Data Through Google Spreadsheet. Special Properties from the JSON transformation. Characteristics of the Migration. Custom Module for Migration.  4- Drupal Migrations (IV): Debugging Migrations First Part\n Basic Debugging of a Drupal Migration: files, database tables and configuration objects. Average Debugging with Migrate Devel Module.  5- Drupal Migrations (V): Debugging Migrations-II\n Introduction to Xdebug. Xdebug for DDEV. Steps for setting up the IDE. Errors launching Drush in containers.   1- Introduction (and remember ETL processes) Well, I would like to start by posing a scenario: imagine that we must migrate a list of taxonomy terms to our Drupal Installation. The source of the data is important, since it defines what type of Plugins we will have to use (source Plugins for the extract, but it can also have influence for the processing and for the final load in destination).\nThe imagined scenario of this article will be the following: we have \u0026ldquo;inherited\u0026rdquo; a migration task already initiated by a previous partner. This migration consists of populating an existing vocabulary in our Drupal installation with taxonomy terms. To practice with other Plugins and other migration models, I thought we can take a Google spreadsheet as a source of data. Let\u0026rsquo;s not forget the frame we\u0026rsquo;re in:\nToday our goal will be to fill the fields of a taxonomy term using the data contained in a external Google Spreadsheet. So we have to complete these fields:\nAnd we\u0026rsquo;re going to do this describing a Migrate Process and executing it as Configuration. Do you know the differences between migrations as code and as configuration? You can learn some keys about this topic here, in the previous article about Migrations.\n2- Exposing data through Google Spreadsheet So now, to perform source data extraction, we\u0026rsquo;ll need a spreadsheet with exposed values. I\u0026rsquo;ve created a Google spreadsheet here at this address. This Google Spreadsheet contains some columns with values related to fields of a taxonomy term, ready for migration.\nIn order to processing the data source we\u0026rsquo;ll use the Migrate Google Sheets contrib module from Drupal.org, so you can run your Composer to download the resource. Just launch:\ncomposer require drupal/migrate_plus #If applicable composer require drupal/migrate_google_sheets drush en migrate_plus migrate_google_sheet -y This contrib module will treat the resource as a JSON file, though for that we have to do some tasks previously. For example, we have to expose the Google Spreadsheet like a JSON datasource, using the tools provided by Google.\n  First, we need extracting the workbook-id of our Spreadsheet: docs.google.com/spreadsheets/d/1bKGbPbgeuXaBfcKetaDqoDimmYcerQY_hT1rqzw4TbM/ -\u0026gt; workbook-id: 1bKGbPbgeuXaBfcKetaDqoDimmYcerQY_hT1rqzw4TbM.\n  Second, we need the worksheet-index too. This is only the index of the tab with data from the Spreadsheet. In this case -\u0026gt; worksheet-index: 1.\n  Third, Building the JSON exposed URL using the pattern: spreadsheets.google.com/feeds/list/[workbook-id]/[worksheet-index]/public/values?alt=json, for us: http://spreadsheets.google.com/feeds/list/1bKGbPbgeuXaBfcKetaDqoDimmYcerQY_hT1rqzw4TbM/1/public/values?alt=json.\n  That\u0026rsquo;s all! now we got an exposed Google Spreadsheet as a JSON file and now we can get the values from the selected Source Plugins of the Drupal Migrate API.\n3- Special Properties from the JSON transformation Now we\u0026rsquo;re seeing our JSON datasource from our browser (maybe better you use some kind of extension for your browser to get a well-formed view of the JSON data, like Firefox is doing by default):\n { \u0026quot;gsx$id\u0026quot;: { \u0026quot;$t\u0026quot;: \u0026quot;1\u0026quot; }, \u0026quot;gsx$name\u0026quot;: { \u0026quot;$t\u0026quot;: \u0026quot;Term 1\u0026quot; }, \u0026quot;gsx$description\u0026quot;: { \u0026quot;$t\u0026quot;: \u0026quot;Descrip 1\u0026quot; }, \u0026quot;gsx$url\u0026quot;: { \u0026quot;$t\u0026quot;: \u0026quot;/t1\u0026quot; } \u0026quot;gsx$published\u0026quot;: { \u0026quot;$t\u0026quot;: \u0026quot;1\u0026quot; } } As we can see, some items has been changed from the original format. Look there:\nAn important observation about this transformation is that the move to JSON format includes some changes over the original spreadsheet. For example, the field identification is modified by Google by adding certain prefixes to the name:\n gsx$ for field names. $t for values stored in fields.  And the headers has been changed:\n \u0026lsquo;Id\u0026rsquo; is now \u0026lsquo;gsx$id\u0026rsquo; \u0026lsquo;Name\u0026rsquo; is now \u0026lsquo;gsx$name\u0026rsquo; \u0026lsquo;Description\u0026rsquo; is now \u0026lsquo;gsx$description\u0026rsquo; \u0026lsquo;Url\u0026rsquo; is now \u0026lsquo;gsx$url\u0026rsquo; \u0026lsquo;Published\u0026rsquo; is now \u0026lsquo;gsx$published\u0026rsquo;  But since these changes are stable, the Plugin takes care of their processing:\n// Class GoogleSheets.php // Module migrate_google_sheets // @see https://git.drupalcode.org/project/migrate_google_sheets/ // Actual values are stored in gsx$\u0026lt;field\u0026gt;['$t']. $this-\u0026gt;currentItem[$field_name] = $current['gsx$' . $selector]['$t']; This allows us use simply the names of the data in their exposed \u0026lsquo;flat\u0026rsquo; transformations: all will be in lowercase and with spaces or special characters removed. In addition, the values are stored in an XPath feed/entry path, which the module will also take over:\n// For Google Sheets, the actual row data lives under feed-\u0026gt;entry. if (isset($array['feed']) \u0026amp;\u0026amp; isset($array['feed']['entry'])) { $array = $array['feed']['entry']; } 4- Characteristics of the Migration The first observation we will make is that the Google Spreadsheet Plugin is actually an extension of the JSON Processing Plugin, as we can see in the class:\nuse Drupal\\Core\\Plugin\\ContainerFactoryPluginInterface; use Drupal\\migrate\\MigrateException; use Drupal\\migrate_plus\\Plugin\\migrate_plus\\data_parser\\JSON; use GuzzleHttp\\Exception\\RequestException; /** * Obtain Google Sheet data for migration. * * @DataParser( * id = \u0026quot;google_sheets\u0026quot;, * title = @Translation(\u0026quot;Google Sheets\u0026quot;) * ) */ class GoogleSheets extends JSON implements ContainerFactoryPluginInterface { ... } Also we can see that the GoogleSheet Plugins is marked as a Data Parser in its block annotations, sowe\u0026rsquo;ll need some more resources: a base source Plugin and a Data Fetcher (then the Google Spreadsheet Plugin will be the third part in the process).\nOk, What Source Plugins are available in my Drupal installation? Let\u0026rsquo;s see. Launching:\ndrupal debug:plugin migrate.source We\u0026rsquo;ll get by prompt:\ndrupal@migrations-web:/var/www/html$ drupal debug:plugin migrate.source --------------- --------------------------------------------------------- Plugin ID Plugin class --------------- --------------------------------------------------------- embedded_data Drupal\\migrate\\Plugin\\migrate\\source\\EmbeddedDataSource empty Drupal\\migrate\\Plugin\\migrate\\source\\EmptySource url Drupal\\migrate_plus\\Plugin\\migrate\\source\\Url --------------- --------------------------------------------------------- drupal@migrations-web:/var/www/html$ Ok, as a Source Plugin we can use the class Url.php (our file is exposed by URL). In the Url.php class, we see that we need some king of data parser (The Google Spread Sheet class).\n /** * The data parser plugin. * * @var \\Drupal\\migrate_plus\\DataParserPluginInterface */ protected $dataParserPlugin; And looking for a fetcher / handler, we can find out a data fetcher for http processing, the class Http.php ready to work with a Url Plugin as source:\n/** * Retrieve data over an HTTP connection for migration. * * Example: * * @code * source: * plugin: url * data_fetcher_plugin: http * headers: * Accept: application/json * User-Agent: Internet Explorer 6 * Authorization-Key: secret * Arbitrary-Header: foobarbaz * @endcode * * @DataFetcher( * id = \u0026quot;http\u0026quot;, * title = @Translation(\u0026quot;HTTP\u0026quot;) * ) */ class Http extends DataFetcherPluginBase implements ContainerFactoryPluginInterface { From another side, seems that the Url.php class requires url directions from its constructor method. We can use single URL directions or a set:\n /** * {@inheritdoc} */ public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrationInterface $migration) { if (!is_array($configuration['urls'])) { $configuration['urls'] = [$configuration['urls']]; } parent::__construct($configuration, $plugin_id, $plugin_definition, $migration); $this-\u0026gt;sourceUrls = $configuration['urls']; } So by putting together the different parts we\u0026rsquo;re looking at, it looks like we\u0026rsquo;ll have to give a structured form to the elements, according to their order and position. In short, our first section for the Source would look like this:\nsource: plugin: url data_fetcher_plugin: http data_parser_plugin: google_sheets urls: 'http://spreadsheets.google.com/feeds/list/1bKGbPbgeuXaBfcKetaDqoDimmYcerQY_hT1rqzw4TbM/1/public/values?alt=json' 5- Custom Module for Migration We\u0026rsquo;ll create a new custom module called migration_google_sheet and with structure:\n/project/web/modules/custom/ \\__migration_google_sheet/ \\__migration_google_sheet.info.yml \\__config/ \\__install/ \\__migrate_plus.migration.migration.taxonomy_google_sheet.yml Migration Description File: migrate_plus.migration.taxonomy_google_sheet.yml\nlangcode: en status: true dependencies: enforced: module: - migration_google_sheet id: taxonomy_google_sheet label: 'Migrating Taxonomy' source: plugin: url data_fetcher_plugin: http data_parser_plugin: google_sheets urls: 'http://spreadsheets.google.com/feeds/list/1bKGbPbgeuXaBfcKetaDqoDimmYcerQY_hT1rqzw4TbM/1/public/values?alt=json' fields: - name: id label: 'Id' selector: id - name: name label: 'Name' selector: name - name: description label: 'Description' selector: description - name: url label: 'Url' selector: url - name: published label: 'Published' selector: published ids: id: type: integer process: name: name description: description path: url status: published destination: plugin: 'entity:taxonomy_term' default_bundle: tags So now, executing the migration from prompt:\ndrush en migration_google_sheet -y drush migrate:import taxonomy_google_sheet [notice] Processed 30 items (30 created, 0 updated, 0 failed, 0 ignored) - done with 'taxonomy_google_sheet' Well done! We just migrated thirty taxonomy terms from a Google spreadsheet:\nYou can download or clone the custom Migration module from my gitlab repository.\n6- :wq! Recommended song   ","permalink":"https://www.therussianlullaby.dev/blog/drupal-migrations-three-migrating-from-google-spreadsheet/","tags":["Drupal Migrations","Migrate API","Contrib Modules","Drupal Plugins","ETL"],"title":"Drupal Migrations (III): Migrating from Google Spreadsheet"},{"categories":["Development"],"contents":"Everything around Test-driven development (TDD) is a very interesting and very motivating world and besides, these are topics with a certain antiquity. So it is very easy to find related contents. But the relationship between this area and Drupal is even more interesting: there are multiple options to implement testing of different types and different orientation (Unit Test, Kernel, JavaScript, functional \u0026hellip;) so it can be complex to introduce in this world. Today I want to take advantage of the experience of porting to Drupal 8 a small contributed module to share examples about browser-focused Functional Testing in Drupal 8 (or Drupal 9). We will learn together with simple use cases. It may be a small slice (Functional Testing of a very small features) of a big cake (Testing in Drupal), but it will be a nice entry point. Follow me.\n Picture from Unsplash, user National Cancer Institute, @nci\n Table of Contents\n1- Introduction 2- Arrangements 3- The BrowserTestBase class 4- Basic Scaffolding 5- Your tests 6- Running the test 7- Read More 8- :wq!\n 1- Introduction Well, as I said in the obligatory introductory paragraph, everything related to Testing is extensive, very broad and combines an ungraspable conjunction of philosophical-theoretical elements with practical-technical issues\u0026hellip;so I guess that\u0026rsquo;s why I was so happy when in the context of the migration to Drupal 8|9 of the contributed module humans.txt, Pedro Cambra as its maintainer proposed in an issue to provide the module with a certain type of test. It was a great opportunity to use it as a simple, didactic and intuitive approach.\nAbout the testing we are going to see in this article, it is important to situate it and give it context: we will make some types of browser tests, which are part of the test types that come from PHPUnit classes and resources. How are they related? Let\u0026rsquo;s see this introduction made by James G. Robertson in the Atlantic BT website:\n PHPUnit can handle different types of tests in Drupal core: unit tests, kernel tests, and functional tests. With these three tests, you can confirm the quality and reaction of code on edge cases in different layers. Unit tests test functions, methods, and classes without requiring a database connection to run. On the other hand, kernel tests test the integration of module APIs and require a database connection to be configured to run. Functional tests test the entire system and require more setup than the others. Within the functional tests, there are both Browser and JavaScript tests. In addition to these PHP-based tests, you may also run core JavaScript tests using the Nightwatch framework.\n  Source: Atlantic BT, A full guide to phpunit in Drupal 8.  Right, so what we\u0026rsquo;re going to do in this case has to do with testing actions to be performed in the web interface of our Drupal installation but running through code using classes that already provide methods for replicating \u0026ldquo;manual actions\u0026rdquo;. Intuitively\u0026hellip; What can we think that we will need? Let\u0026rsquo;s advance a possible outline of our possible actions:\n Maybe, loading a URL. Perhaps, pressing buttons on a form. We may need to load values into this former form. Or also check users, roles and permissions.  So we\u0026rsquo;ll need classes and resources that allow us to reproduce these actions through code (so that they can be automated). This is just a sketch of our possible needs, as we must first be clear about which features we want to test. So the first guideline shared in this introduction will be: You must know well your needs to test.\nOk, and what is the features we\u0026rsquo;ll have to test? the Humans.txt contrib module was created years ago to offers a way for building the humans.txt file from within Drupal. For several months we have been working on its portability to Drupal 8 and its features are summarized in two main tasks:\n Generating an object as a humans.txt file with values loaded from a configuration form. Offering to create a link to the object/file in the \u0026lt;head\u0026gt; section of pages.  Mainly, these will be the features that we will have to test.\nThis article is intended as a theoretical - practical guide to browser-based functional testing for Drupal, based on a certain testing issue history of the Humans.txt module. It includes bugs, more bugs, errors, misses, mismatched versions\u0026hellip;uploads and more uploads, testing bots\u0026hellip;and above all\u0026hellip; a conversational sample of pair between a maintainer and a contributor when working in parallel. It may not be very agile, but it is very real. And I think that here is the most interesting part of the article: exposing the work cycle combined with all the successes and failures.\n2- Arrangements Well, in this section I\u0026rsquo;ve included the fun little story about how I discovered that I didn\u0026rsquo;t have the necessary resources installed in the test environment I chose. Pay attention.\nIt all started when I switched environments and realized that I didn\u0026rsquo;t have phpunit installed\u0026hellip;I always use DDEV (- Get more info about DDEV - ) as a tool for creating local development environments and in this case I switched to one that didn\u0026rsquo;t have a Drupal installation with the \u0026ndash;dev resources. So if this is your case, take advantage and review the following steps.\nEnvironment\n  First, stop your local apache, if exists: sudo /etc/init.d/apache2 stop\n  Second, up with your DDEV project: ddev start. Make sure your DDEV project is up, running and functioning normally. You\u0026rsquo;re going to execute PHPUnit from within the DDEV main web container and you\u0026rsquo;ll use the db container too.\n  Third, go to the web container and install with composer the next resources:\nddev ssh composer require phpunit/phpunit   Note: In my first iteration, I launched one request like this (opening the phpunit\u0026rsquo;s version to the latest available) and it caused me a lot of problems when trying to use PHPUnit:\nCould not use \u0026#34;\\Drupal\\Tests\\Listeners\\HtmlOutputPrinter\u0026#34; as printer: class does not exist PHPUnit 9.1.1 What\u0026rsquo;s the problem? Drupal 8.x is not compatible with PHPUnit in that version (In fact there\u0026rsquo;are some issues proposing an update to PHPUnit 8 for Drupal 9, like this), so it\u0026rsquo;s better to uninstall it and request a slightly more limited version, which is not the last one, something around PHPUnit 7 for example.\nFor the rest of the dependencies, I recognize that I was playing to solve them just following the successive error messages trying to launch several tests again, like Class \u0026quot;Symfony\\Bridge\\PhpUnit\\SymfonyTestsListener\u0026quot; does not exist and many others.\nIn Drupal 8, I\u0026rsquo;ve requested the next resources (update versions in Drupal 9 installations):\n$ composer require --dev phpunit/phpunit:^7 --with-all-dependencies $ composer require --dev symfony/phpunit-bridge $ composer require --dev behat/mink-goutte-driver $ composer require --dev behat/mink-selenium2-driver In summary I have installed all the following resources in the approximate versions indicated:\ncomposer remove phpunit/phpunit composer require phpunit/phpunit:^7 # In my case, this installed phpunit 7.5.20 composer require symfony/phpunit-bridge:^3.4.3 composer require behat/behat:^3.4 composer require behat/mink:^1.8 composer require behat/mink-goutte-driver:^1.2 PHPUnit in Drupal Core\nThe next step was adjust the use of PHPUnit from the file provided by Drupal, present in /project/web/core/phpunit.xml.dist. It\u0026rsquo;s necessary to make a copy of the file and rename it as phpunit.xml simply, leaving this copy in the same location /core.\nNow we need modify two environment variables within the copied file. Beware:\n\u0026lt;!-- Example SIMPLETEST_BASE_URL value: http://localhost --\u0026gt; \u0026lt;env name=\u0026quot;SIMPLETEST_BASE_URL\u0026quot; value=\u0026quot;http://localhost\u0026quot;/\u0026gt; \u0026lt;!-- Example SIMPLETEST_DB value: mysql://username:password@localhost/databasename#table_prefix --\u0026gt; \u0026lt;env name=\u0026quot;SIMPLETEST_DB\u0026quot; value=\u0026quot;mysql://db:db@db/db\u0026quot;/\u0026gt; As you can see, now we need an internal URL and all the connection data to your database. Ok, in my case as I\u0026rsquo;m using DDEV I can extract the information from my prompt, just launching ddev describe. Remember that we\u0026rsquo;re looking for a string formatted as: mysql://username:password@localhost/databasename. And in the DDEV context, by default, all these data are the same: db. Let\u0026rsquo;s see:\nLike an extra, I\u0026rsquo;ve configured a folder to save results from tests in a HTML format using the variables:\n\u0026lt;env name=\u0026quot;BROWSERTEST_OUTPUT_DIRECTORY\u0026quot; value=\u0026quot;/var/www/html/web/sites/default/simpletest/browser_output/\u0026quot;/\u0026gt; \u0026lt;env name=\u0026quot;BROWSERTEST_OUTPUT_BASE_URL\u0026quot; value=\u0026quot;http://migrations.ddev.site\u0026quot;/\u0026gt; Where BROWSERTEST_OUTPUT_DIRECTORY is used as the directory where the output data will be saved by PHPUnit and needs to be an absolute local path, in my case the result HTML from test goes to /var/www/html/web/sites/default/simpletest/browser_output/ and while BROWSERTEST_OUTPUT_BASE_URL help to register the future links to the saved output, in my case is: migrations.ddev.site and is just the name of the DDEV project that I\u0026rsquo;m using. This will write all the output from the executed test in the directory using HTML format, and so you can see the pages that the emulated browser visited during the test. Theses HTML pages are saved as files and linked under the URL.\nWhen you launch the test, Drupal 8 will use a simulated browser to execute actions and check assertions, is like an emulator offered by Mink, just a pure headless browser from the installed dependency behat/mink. Now remember that you must ensure the write permissions in the destiny folder for the user that will launch the test. You can see my whole configuration for PHPUnit using DDEV here in the next gist:\n So the second guideline shared in this section will be: tune up your environment well.\nWhen you have available all the libraries and dependencies along with the configuration of the phpunit.xml file, you can try to run the tests that bring many modules through a console execution instruction. But first you\u0026rsquo;ll need to login in the web container from DDEV:\n$ ddev ssh The format of the instruction we will use will be related to the position for ourselves within the path /project/web/ and launch the call to phpunit in a format like this:\ndavidjguru@project-container:/var/www/html/web$ ../vendor/bin/phpunit -c core modules/contrib/config_inspector Where the param -c points to the localization of the phpunit.xm file and it will run all the test located in the marked direction (in this example it will be execute tests from the Config Inspector Contrib Module).\nUPDATE (15/04/2020) Well, After a change in the renaming of the /test folder to /tests, the Drupal.org Testing bot has started working and has detected\u0026hellip;that two methods I used in the Test Class were not available, they didn\u0026rsquo;t exist. At that moment I thought about the version of phpunit I was using (and that I chose and installed myself) and as recommended for Drupal 8 it should be PHPUnit 6.5 and not 7.5 as I am using. So downgrading and adapt the methods to others available\u0026hellip;Which are?\nThese methods:\n$this-\u0026gt;assertStringNotContainsString($this-\u0026gt;fileLink, $tags, sprintf('Humans.txt link: [%s] is not shown in the -head- section.', $this-\u0026gt;fileLink)); $this-\u0026gt;assertStringContainsString($this-\u0026gt;fileLink, $tags, sprintf('Humans.txt link: [%s] is shown in the -head- section.', $this-\u0026gt;fileLink)); Should be replaced by these others, compatible with PHPUnit 6.5:\n$this-\u0026gt;assertNotContains($this-\u0026gt;fileLink, $tags, sprintf('Humans.txt link: [%s] is not shown in the -head- section.', $this-\u0026gt;fileLink)); $this-\u0026gt;assertContains($this-\u0026gt;fileLink, $tags, sprintf('Humans.txt link: [%s] is shown in the -head- section.', $this-\u0026gt;fileLink)); From the Assert.php Class available in vendor/phpunit/phpunit/src/Franework/Assert.php\nSo note to my future me: Drupal 8 + PHPUnit 6.5, man.\n3- The BrowserTestBase class Now we are going to deal with a fundamental PHP class for this test case we are going to make, the BrowserTestBase.php class of the Drupal core test context, path: /core/tests/Drupal/Tests/BrowserTestBase.php (Don\u0026rsquo;t confuse it with an already deprecated version from the context of the simpletest core module called so BrowserTestBase).\nThis abstract class extends the TestCase class from PHPUnit (one of the main reasons why we need the use of PHPUnit here) and uses a lot of PHP traits providing many methods from different origins, finally grouped by this base class that we\u0026rsquo;ll have to extend for our goals. In general terms, we know that abstract classes are classes that are not instantiated and can only be inherited, thus transferring an obligatory functioning to the daughter classes. In this example, using BrowserTestBase we\u0026rsquo;ll have nearly forty methods available within the class (there are dozens of possible assertions) to perform specific checks on actions to be performed through the browser.\nThe info from the class is very explicit: You must use the class for functional Drupal test (ok), You have to put your new classes extending BrowserTestBase as a base class in path: /modules/custom/your_custom_module/test/src/Functional and avoid using t() function for translate unless you\u0026rsquo;re testing translation functionality.\n/** * Provides a test case for functional Drupal tests. * * Tests extending BrowserTestBase must exist in the * Drupal\\Tests\\yourmodule\\Functional namespace and live in the * modules/yourmodule/tests/src/Functional directory. * * Tests extending this base class should only translate text when testing * translation functionality. For example, avoid wrapping test text with t() * or TranslatableMarkup(). * * @ingroup testing */ Remember what we said before about knowing well what kind of actions you want to test? Think that this class will provide us with functions that will align possible actions with concrete methods to reproduce mechanics through code. For example:\n Your test needs to create new users? use the drupalCreateUser() method. Your test needs to login to the Drupal installation? use the drupalLogin() method. Your test needs to check if a form field is being rendered? you can use: assertSession()-\u0026gt;fieldExists(\u0026lsquo;field_machine_name\u0026rsquo;).  As you see, the possibilities are many and only require that we have some knowledge of the capabilities that the BrowserTestBase class offers to automate our tests.\n4- Basic scaffolding I will now share some actions that I need to do before I can start testing functions. As my goal is to prepare test for the contrib module \u0026ldquo;Humans.txt\u0026rdquo; the first thing I will do is download the module, install it and make sure I\u0026rsquo;m in the last commit of the branch I\u0026rsquo;m interested in testing (the one in version 8.x).\nAs I\u0026rsquo;m in a DDEV based context the first thing I\u0026rsquo;ll do is to access my web container and from there perform the rest of initial commands:\nddev ssh composer require drupal/humanstxt drupal moi humanstxt cd modules/contrib/humanstxt git checkout 8.x-1.x Now I\u0026rsquo;m in the initial point for my job. Another aspect that I must evaluate is if it is convenient (or not) to create a submodule inside Humanstxt as a \u0026ldquo;test\u0026rdquo;, with its own installation and testing paths that respond to the same controllers as the main module, or if on the contrary it\u0026rsquo;s excessive (for now) for the testing of this module. At this moment I think I will create the tests directly, testing against the resources of the main module.\n5- Your tests In our current context, using a new class HumansTxtBasicText which extends BrowserTestBase, every method inside our class will be a unique test by itself, but there will a lot of assertions more to check, cause of in one of our used functions, we\u0026rsquo;re calling some internal assertions. For example, if we\u0026rsquo;re using the function drupalCreateUser(['permission name']) from the UserCreationTrait and originally named as createUser , with our direct assertions, we\u0026rsquo;re going to check other internals just like:\n$valid_user = $account-\u0026gt;id() !== NULL; $this-\u0026gt;assertTrue($valid_user, new FormattableMarkup('User created with name %name and pass %pass', ['%name' =\u0026gt; $edit['name'], '%pass' =\u0026gt; $edit['pass']]), 'User login'); Due to this, you\u0026rsquo;ll see more assertions than yours (or the explicit yours), like this feedback when I\u0026rsquo;m only using four explicit assertions:\nTesting modules/contrib/humanstxt .. 2 / 2 (100%) Time: 1.12 minutes, Memory: 4.00 MB OK (2 tests, 16 assertions) Ok, don\u0026rsquo;t fear. Let\u0026rsquo;s move on.\nThe next important point to know is that all your methods should start with the word \u0026lsquo;test\u0026rsquo; in lowercase. So any method using this as prefix with public visibility will be automatically detected by PHPUnit and will be launched too.\nAnd there\u0026rsquo;s one more important thing: any stuff you make within a method/test, won\u0026rsquo;t live outside it. So, for example if in a test you\u0026rsquo;re creating a new user, then in the next test you\u0026rsquo;ll have to create it again. Ok? That\u0026rsquo;s because every time you run a method/test, each test function will have a full new Drupal instance to execute tests.\nThinking about how to do it in an organized way, I thought about doing it in three functional blocks:\n  First, check the access control to the configuration form and its fields, that is, check the behavior for different roles (administrators, users with basic and anonymous permissions).\n  Then, check complementary questions with the information collected in the headers of the answers to requests or the cache tags stored.\n  Finally, test if the file is created, kept accessible and its link is located in the right way. Everything for different user profiles: administrators, basic and anonymous permissions.\n  So, I\u0026rsquo;ll create an new folder within the module with path: humanstxt/test/src/Functional/, using a new class called HumansTxtBasicTest.php with namespace: Drupal\\Tests\\humanstxt\\Functional.\nPhase one: Checking access control We\u0026rsquo;re going to test if three different users, one with admin permissions, other as a basic user without specific permissions and a last anonymous user can reach the configuration page for the Humans.txt module. Theoretically, only the admin user can access and the basic user or the anonymous cannot reach the config page. Let\u0026rsquo;s see.\nFirst I\u0026rsquo;m going to test access for admins:\n /** * Checks if an admin user can access to the configuration page. */ public function testHumansTxtAdminAccess() { // Build initial paths. $humanstxt_config = Url::fromRoute('humanstxt.admin_settings_form', [], ['absolute' =\u0026gt; FALSE])-\u0026gt;toString(); // Create user for testing. $this-\u0026gt;adminUser = $this-\u0026gt;drupalCreateUser(['administer humans.txt']); // Login for the former admin user. $this-\u0026gt;drupalLogin($this-\u0026gt;adminUser); // Access to the path of humanstxt config page. $this-\u0026gt;drupalGet($humanstxt_config); // Check the response returned by Drupal. $this-\u0026gt;assertResponse(200); } I\u0026rsquo;m using the Url class cause I don\u0026rsquo;t like work with explicit paths in testing. So, if a path changes in a routing.yml file, the test continues being valid and it will be run normally.\nAnd then, I\u0026rsquo;ll check the access for the pair of users no-admin, using the same test:\n /** * Checks if a non-administrative user cannot access to the config page. */ public function testHumansTxtUserNoAccess() { // Build initial path. $humanstxt_config = Url::fromRoute('humanstxt.admin_settings_form', [], ['absolute' =\u0026gt; FALSE])-\u0026gt;toString(); // Create user for testing. $this-\u0026gt;normalUser = $this-\u0026gt;drupalCreateUser(['access content']); // Login for the former basic user. $this-\u0026gt;drupalLogin($this-\u0026gt;normalUser); // Try access to the path of humanstxt config page. $this-\u0026gt;drupalGet($humanstxt_config); // Check the response returned by Drupal. $this-\u0026gt;assertResponse(403); // Logout as normal user and repeat the former cycle as anonymous user. $this-\u0026gt;drupalLogout(); $this-\u0026gt;drupalGet($humanstxt_config); $this-\u0026gt;assertResponse(403); } Now It\u0026rsquo;s time to check the access to the fields of the configuration form.\n /** * Checks if an administrator can see the fields. */ public function testHumansTxtAdminFields() { // Build initial path. $humanstxt_config = Url::fromRoute('humanstxt.admin_settings_form', [], ['absolute' =\u0026gt; FALSE])-\u0026gt;toString(); // Create user for testing. $this-\u0026gt;adminUser = $this-\u0026gt;drupalCreateUser(['administer humans.txt']); // Login for the the former admin user. $this-\u0026gt;drupalLogin($this-\u0026gt;adminUser); // Access to the path of humanstxt config page. $this-\u0026gt;drupalGet($humanstxt_config); // The textarea for configuring humans.txt is shown. $this-\u0026gt;assertSession()-\u0026gt;fieldExists('humanstxt_content'); // The checkbox for configuring the creation of the humanstxt link is shown. $this-\u0026gt;assertSession()-\u0026gt;fieldExists('humanstxt_display_link'); } /** * Checks if a non-administrative user cannot use the configuration page. */ public function testHumansTxtUserFields() { // Build initial path. $humanstxt_config = Url::fromRoute('humanstxt.admin_settings_form', [], ['absolute' =\u0026gt; FALSE])-\u0026gt;toString(); // Create user for testing. $this-\u0026gt;normalUser = $this-\u0026gt;drupalCreateUser(['access content']); // Login for the former basic user. $this-\u0026gt;drupalLogin($this-\u0026gt;normalUser); // Access to the path of humanstxt config page. $this-\u0026gt;drupalGet($humanstxt_config); // The textarea is not shown for basic users. $this-\u0026gt;assertNoFieldById('edit-humanstxt-content', NULL); // The checkbox is not shown for basic users. $this-\u0026gt;assertNoFieldById('edit-humanstxt-display-link', NULL); } Phase Two: Checking Complementary Items In this block I would like to check stuff like the headers of the returned object/file or the cache tags.\n /** * Checks if the header is right. */ public function testHumansTxtHeader() { // Build initial path. $humanstxt_path = Url::fromRoute('humanstxt.content', [], ['absolute' =\u0026gt; FALSE])-\u0026gt;toString(); // Access to the path of humans.txt object/file. $this-\u0026gt;drupalGet($humanstxt_path); // Check the returned response. $this-\u0026gt;assertResponse(200); // Check if the file was served as text/plain with charset=UTF-8. $this-\u0026gt;assertHeader('Content-Type', 'text/plain; charset=UTF-8'); } /** * Checks if cache tags exists. */ public function testHumansTxtCacheTags() { // Build initial path. $humanstxt_path = Url::fromRoute('humanstxt.content', [], ['absolute' =\u0026gt; FALSE])-\u0026gt;toString(); // Access to the path of humans.txt object/file. $this-\u0026gt;drupalGet($humanstxt_path); // Check the returned response. $this-\u0026gt;assertResponse(200); // Check the related cache tag. $this-\u0026gt;assertCacheTag('humanstxt'); } Phase Three: Checking the delivered content Ok, to check the content file or the link write in the section, I thought to make a central and unified test that would group everything for the different user profiles to be tested. What kind of things am I interested in checking? Well I would like to test if the access to the configuration form is possible for administrators (although this was already tested in a previous test) and then creating a new configuration of the Humans.txt to test if the saved content corresponds with the one visible inside the object/file (testing also the access to the file). Finally I would like to test if the link associated to the meta tag in the section of the pages is being loaded, choosing one at random.\n /** * Checks if humans.txt file is delivered for Different Users as was configured. */ public function testHumansTxtConfigureHumansTxtDifferentUsers() { // Build initial paths. $humanstxt_config = Url::fromRoute('humanstxt.admin_settings_form', [], ['absolute' =\u0026gt; FALSE])-\u0026gt;toString(); $humanstxt_path = Url::fromRoute('humanstxt.content', [], ['absolute' =\u0026gt; TRUE])-\u0026gt;toString(); $humanstxt_link = '\u0026lt;link rel=\u0026quot;author\u0026quot; type=\u0026quot;text/plain\u0026quot; hreflang=\u0026quot;x-default\u0026quot; href=\u0026quot;' . $humanstxt_path . '\u0026quot;\u0026gt;'; // Create users for testing. $this-\u0026gt;adminUser = $this-\u0026gt;drupalCreateUser(['administer humans.txt']); $this-\u0026gt;normalUser = $this-\u0026gt;drupalCreateUser(['access content']); // Login as admin and get the config form page. $this-\u0026gt;drupalLogin($this-\u0026gt;adminUser); $this-\u0026gt;drupalGet($humanstxt_config); // Load a new configuration for Humans.txt file and submit config Form. $test_string = \u0026quot;# Testing Humans.txt {$this-\u0026gt;randomMachineName()}\u0026quot;; $this-\u0026gt;submitForm(['humanstxt_content' =\u0026gt; $test_string, 'humanstxt_display_link' =\u0026gt; TRUE], t('Save configuration')); // Check the object/file created. $this-\u0026gt;drupalGet($humanstxt_path); $this-\u0026gt;assertResponse(200); // Test header. $this-\u0026gt;assertHeader('Content-Type', 'text/plain; charset=UTF-8'); // Get page content. $content = $this-\u0026gt;getSession()-\u0026gt;getPage()-\u0026gt;getContent(); // Test the assert- if exists the test_string in the content. $this-\u0026gt;assertTrue($content == $test_string, sprintf('Test string [%s] is shown in the configured humans.txt file [%s].', $test_string, $content)); // Test if the link to the object/file is in HTML \u0026lt;head\u0026gt; section for Admins. $this-\u0026gt;drupalGet($humanstxt_config); $this-\u0026gt;assertResponse(200); $tags = $this-\u0026gt;getSession()-\u0026gt;getPage()-\u0026gt;getHtml(); $this-\u0026gt;assertStringContainsString($humanstxt_link, $tags, sprintf('Test link [%s] is shown in the HTML -head- section from [%s].', $humanstxt_link, $tags)); // Logout as admin and login as normal user. $this-\u0026gt;drupalLogout(); $this-\u0026gt;drupalLogin($this-\u0026gt;normalUser); // Repeat the previous cycle now as normal user. $this-\u0026gt;drupalGet($humanstxt_path); $this-\u0026gt;assertResponse(200); $this-\u0026gt;assertHeader('Content-Type', 'text/plain; charset=UTF-8'); $content = $this-\u0026gt;getSession()-\u0026gt;getPage()-\u0026gt;getContent(); $this-\u0026gt;assertTrue($content == $test_string, sprintf('Test string [%s] is shown in the configured humans.txt file [%s].', $test_string, $content)); $this-\u0026gt;drupalGet($humanstxt_config); $this-\u0026gt;assertResponse(403); $tags = $this-\u0026gt;getSession()-\u0026gt;getPage()-\u0026gt;getHtml(); $this-\u0026gt;assertStringContainsString($humanstxt_link, $tags, sprintf('Test link [%s] is shown in the HTML -head- section from [%s].', $humanstxt_link, $tags)); // Logout as normal user. $this-\u0026gt;drupalLogout(); // Now a third iteration as anonymous user. $this-\u0026gt;drupalGet($humanstxt_path); $this-\u0026gt;assertResponse(200); $this-\u0026gt;assertHeader('Content-Type', 'text/plain; charset=UTF-8'); $content = $this-\u0026gt;getSession()-\u0026gt;getPage()-\u0026gt;getContent(); $this-\u0026gt;assertTrue($content == $test_string, sprintf('Test string [%s] is shown in the configured humans.txt file [%s].', $test_string, $content)); $this-\u0026gt;drupalGet($humanstxt_config); $this-\u0026gt;assertResponse(403); $tags = $this-\u0026gt;getSession()-\u0026gt;getPage()-\u0026gt;getHtml(); $this-\u0026gt;assertStringContainsString($humanstxt_link, $tags, sprintf('Test link [%s] is shown in the HTML -head- section from [%s].', $humanstxt_link, $tags)); } To check the insertion of the tag in I\u0026rsquo;ve used a double version of the former codeblock, playing with the values of the element from the Configuration Form: 'humanstxt_display_link' =\u0026gt; FALSE and changing it for the next codeblock. This checkbox when false doesn\u0026rsquo;t insert the link to the humans.txt object/file in , and set to TRUE it will put the tag. The occurrence of the mentioned tag in is managed using a special kind of assertion method from PHPUnit called assertStringContainsString, available in my installed version of the testing framework (7.5) and its inverse for negative versions: assertStringNotContainsString().\nSo my idea is using the getSession() method from BrowserTestBase class which returns a Mink Session Object. This session object from Mink offers a method called getPage() that can return an object DocumentElement and use its method getHtml() that returns al the HTML code formatted as string. All of this is in the line: $tags = $this-\u0026gt;getSession()-\u0026gt;getPage()-\u0026gt;getHtml(); and in the variable $tags I\u0026rsquo;ll save all the HTML response ready to search the link, using the variable as haystack. In any case, all the HTML code from a page is too much for processing, and we don\u0026rsquo;t need to get all the HTML code, so we\u0026rsquo;ll get a substring cutting the output from the getHtml() method, extracting up to two thousand characters, enough to evaluate the whole section.\n// All the HTML code is too much, we just need to inspect the \u0026lt;head\u0026gt; section. $tags = substr($this-\u0026gt;getSession()-\u0026gt;getPage()-\u0026gt;getHtml(), 0, 2020); $this-\u0026gt;assertStringContainsString($humanstxt_link, $tags, sprintf('Test link: [%s] is NOT shown in the head section from [%s] and this shouldn\\'t happen.', $humanstxt_link, $tags)); Finally you can see this first version of the TestClass as Gist in GitHub. After uploading the patch to the issue there will surely be revisions and changes to the patch, so I promise to link the final version of the Test class that will be committed to the 8.x-1.x branch.\nFirst version of the Testing Class in Gist.\nUPDATE (14/04/2020) After the first feedback from the maintainer, I have taken all his indications and prepared a new refactored version of the Test class. I\u0026rsquo;ve reduced, adapted and simplified several things following the feedback from the maintainer. Now the class is using the setUp() method, the numbers of methods was reduced from eight to five, the assertions from hundred-six to sixty-four and the codelines from almost three hundred lines to hundred and forty lines of code.\nI\u0026rsquo;m also using simple paths, except in the path to the humans.txt file to be inserted in the section as link, since originally the absolute path to the file is being saved: see this Issue and the last uploaded patch.\nThis second reduced version of the class seems to pass the tests well and gives positive results in all tests and assertions.\n Many thanks to Pedro Cambra from Cambrico for the feedback and revisions, I am very grateful.\nHere is the second version of the test class after refactoring:\nSecond version of the Testing Class in Gist.\nUPDATE (15/04/2020) After starting the Drupal.org testing bot and checking that my version of PHPUnit was not the one needed for the Drupal core, and has been necessary to make changes in two methods. The semantics of the messages in those methods were set for testing by me and were confusing, were also wrong. They have been changed and now the tests are going well.\nThis will be the final version of the class of Tests that will be committed to the repository:  By the way, with this last commit, the migration to Drupal 8 of the contributed module Humans.txt has been completed. The module has been finally ported to Drupal 8 | Drupal 9. Humans.txt Releases. Mission accomplished.\n6- Running the test Well, and now with our test stored and the phpunit configuration initially resolved, it\u0026rsquo;s time to run our test and observe the results. First I\u0026rsquo;m connecting to the DDEV web container by doing $ ddev ssh and I\u0026rsquo;m going to execute test from inside the container. To do this, in my case I\u0026rsquo;m located in /project/web/ but in the DDEV web container, the context will be /var/www/html/ and with phpunit.xml placed in /project/web/core/, that is, from /var/www/html/web/core. I\u0026rsquo;ll launch the instruction:\ndavidjguru@ddevcontainer-web:/var/www/html$ ../vendor/bin/phpunit -c core modules/contrib/humanstxt Out of the DDEV web container, I can launch something like this:\n$ ddev exec ./vendor/bin/phpunit -c web/core /var/www/html/web/modules/contrib/humanstxt Considering that we are executing from /var/www/html/ when we want DDEV launch something and that we have to reach your module. Which throws all the test located inside the humanstxt contrib module\u0026hellip;\nAs we can see, we\u0026rsquo;re executing eight tests with hundred and six assertions. Due to our phpunit.xml configuration, these test are writing over sixty four HTML files based on results using the browser emulator from Mink, thanks to which you will be able to reproduce certain actions by opening the files in your web browser.\nIn the next caption we can see the HTML output nº64 generated from one of last assertions in the code, just when an anonymous user try to get the Humanstxt configuration form and receive and error message with code HTTP 403 \u0026lsquo;Forbidden\u0026rsquo;:\nAs you can see, you can move through the files using the Previous | Next Links in header, connecting all the secuence of HTML results from tests.\n7- Read More  PHPUnit in Drupal 8 PHPUnit Browser Test tutorial in Drupal.org. Automated Test using PHPUnit and Nightwatch.js in Drupal.org. Running PHPUnit tests in Drupal, from Drupal.org documentation. Running Drupal\u0026rsquo;s PHPUnit test suites on DDEV, by Matt Glaman. Mink at a glance, from Mink documentation. Developing Web Applications with Behat and Mink  8- :wq! Recommended song   ","permalink":"https://www.therussianlullaby.dev/blog/functional-testing-for-browser-in-drupal-using-phpunit/","tags":["Drupal Development","PHP","Drupal Testing","Contrib Modules","Backend"],"title":"Functional testing for Browser in Drupal 8-9 using PHPUnit"},{"categories":["Development"],"contents":"Recently I was preparing some events to intercept requests in Symfony and sharing some approaches with my colleagues. Then I discovered that in my environment the topic of Event Management in Drupal (dispatching events, subscribing events) was not very known, so I prepared some snippets to share and from there I thought to write some introductory article. Undoubtedly, in the latest versions of Drupal, certain components of Symfony have not only made their appearance, but also have been gaining importance and surely this will extend further in time, given its elasticity and fully integrated OOP approach. One of these components is the HttpKernel, a very valuable subsystem for building request-response relationships in a technology (Silex, Symfony, Drupal).\n Picture from Unsplash, user Noiseporn, @noiseporn\n Table of Contents\n1- Introduction 2- Concepts of Event and Event Subscriber 3- Building the Event Dispatcher 4- Building the Event Subscriber 5- Read More 6- :wq!\n 1- Introduction As we said, HttpKernel very flexible that lets us structure relationships requests - response. Let\u0026rsquo;s see what it says about itself in its Symfony documentation:\n The HttpKernel component provides a structured process for converting a Request into a Response by making use of the EventDispatcher component. It\u0026rsquo;s flexible enough to create a full-stack framework (Symfony), a micro-framework (Silex) or an advanced CMS system (Drupal).\n In this context where I wanted to practice a little bit with the concepts of \u0026ldquo;Event\u0026rdquo; and \u0026ldquo;Event Subscription\u0026rdquo;. Let\u0026rsquo;s see.\n2- Event and Event Subscriber Concept of Event: Event systems are ways to allow extensions in many applications. Generally there are some similar topics and concepts, which usually include common elements:\n Event Registry: Just a place where event subscribers will be grouped. Event Subscribers / Event Listeners: Callable functions or methods that react to an event. Event Dispatcher: The way the event is triggered. Event Context: Some information shared for the subscribers of an event.  Getting information about services and events: Services? Events? but\u0026hellip;how do I know how many I have available at my Drupal installation? Well you can use Drupal Console with a pair of commands to get the info:\ndrupal debug:container #Get list of available services in a Drupal site. drupal debug:event #Get a list of available events in your Drupal site. Events in Drupal: From Drupal 8, the Symfony Event Dispatcher is one the most important components in a Drupal installation. This component is responsible for launching notifications from your applications. You can be listening these notifications and returning some responses executing your custom code. In Drupal 8, events are defined in a class and declared in a YML file, then dispatched using a function call on the core Drupal event_dispatcher service.\nWhat is an Event Subscriber: A very common task in the Drupal development it\u0026rsquo;s intercept some request from the browser to your Drupal, getting something interesting on it, and then redirect the user to another location in the site.\nWell, in Drupal 7 we were using a combination of hook_init() with a weird function called drupal_goto(), but now in Drupal \u0026gt;= 8 we\u0026rsquo;re doing this using with the symfony model for subscribing us to the kernel.request event.\nWe can launch a redirect from a Controller, but in this case we want to test the topic of Event-Subscribers in Drupal.\n3- Building the Event Dispatcher Note: The example requires that you have previously created a new custom module with its custom_module.info.yml in your Drupal installation.\nNote II: This example is from Drupal 8 Module Development (second edition) by Daniel Sipos, the Holy Bible of the Drupal Development. Pag 65: \u0026ldquo;We will create an event to be dispatched whenever our HelloWorldSalutation::getSalutation() method is called. The purpose is to inform other modules that this happened and potentially allow them to alter the message.\u0026quot;\n3.1- Creating an event class File: SalutationEvent.php Location: /custom_module/\n\u0026lt;?php namespace Drupal\\custom_module; use Symfony\\Component\\EventDispatcher\\Event; /** *Event class to be dispatched from the HelloWorldSalutation service. */ class SalutationEvent extends Event { const EVENT = 'hello_world.salutation_event'; /** * The salutation message. * * @var string */ protected $message; /** * @return mixed */ public function getValue() { return $this-\u0026gt;message; } /** * @param mixed $message */ public function setValue($message) { $this-\u0026gt;message = $message; } } 3.2- Injecting the Event Dispatcher service services: hello_world.salutation: class: \u0026#39;Drupal\\hello_world\\HelloWorldSalutation\u0026#39; arguments: [\u0026#39;@config.factory\u0026#39;, \u0026#39;@event_dispatcher\u0026#39;] tags: - { name: salutation } 4- Building the Event Subscriber Given an internal path in Drupal, we\u0026rsquo;re going to define a new event subscriber to catch the request, review certain conditions of the current user of Drupal and if there\u0026rsquo;s a matching, then we\u0026rsquo;ll set a new redirect to another route in your system bypassing the process.\nWe know that the system allows subscribers to change data before the business logic uses it for something. So to register the new event subscriber, you have to create a new service related to the former class (that implements the interface) and tagged with the event_subscriber key.\nIn this example, when a user requests our custom route, we\u0026rsquo;ll check if the current user has assigned a \u0026ldquo;non_grata\u0026rdquo; role and if the result is positive, then we\u0026rsquo;ll redirect him to the home page of the web portal, without allowing him to access our route. To execute this, we\u0026rsquo;ll need two injected services: the AccountProxyInterface to get the user related data and CurrentRouteMatch to test the requested route.\nRequired parts:\n A declared internal route (file custom_module.routing.yml in custom_module/) A new declared service (file custom_module.services.yml in custom_module/) A new class CustomModuleRedirectSubscriber implementing the EventsSubscriberInterface (file in custom_module/src/EventSubscriber/)  Note: The example requires that you have previously created a new custom module with its custom_module.info.yml to register in your Drupal.\n4.1- The route custom_module.hello: path: \u0026#39;/hello\u0026#39; defaults: _controller: \u0026#39;\\Drupal\\custom_module\\Controller\\CustomModuleController::helloWorld\u0026#39; _title: \u0026#39;Our Testing Route\u0026#39; requirements: _permission: \u0026#39;access content\u0026#39; 4.2- The Service custom_module.redirect_subscriber: class: \u0026#39;\\Drupal\\custom_module\\EventSubscriber\\CustomModuleRedirectSubscriber\u0026#39; arguments: [\u0026#39;@current_user\u0026#39;, \u0026#39;@current_route_match\u0026#39;] tags: - { name: event_subscriber } 4.3- The Class CustomModuleRedirectSubscriber.php \u0026lt;?php namespace Drupal\\custom_module\\EventSubscriber; use Drupal\\Core\\Routing\\CurrentRouteMatch; use Drupal\\Core\\Routing\\LocalRedirectResponse; use Symfony\\Component\\EventDispatcher\\EventSubscriberInterface; use Drupal\\Core\\Session\\AccountProxyInterface; use Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent; use Symfony\\Component\\HttpKernel\\KernelEvents; /** * Class CustomModuleRedirectSubscriber Subscribes to the Kernel Request events. */ class CustomModuleRedirectSubscriber implements EventSubscriberInterface { /** * @var \\Drupal\\Core\\Session\\AccountProxyInterface */ protected $currentUser; /** * @var \\Drupal\\Core\\Routing\\CurrentRouteMatch */ protected $currentRouteMatch; /** * HelloWorldRedirectSubscriber constructor. * * @param \\Drupal\\Core\\Session\\AccountProxyInterface $currentUser * @param CurrentRouteMatch $currentRouteMatch */ public function __construct(AccountProxyInterface $currentUser, CurrentRouteMatch $currentRouteMatch) { $this-\u0026gt;currentUser = $currentUser; $this-\u0026gt;currentRouteMatch = $currentRouteMatch; } /** * {@inheritdoc} */ public static function getSubscribedEvents() { $events[KernelEvents::REQUEST][] = ['onRequest', 0]; return $events; } /** * Handler for the kernel request event. * * @param \\Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent $event * */ public function onRequest(GetResponseEvent $event){ $route_name = $this-\u0026gt;currentRouteMatch-\u0026gt;getRouteName(); if ($route_name !== 'custom_module.hello') { return; } $roles = $this-\u0026gt;currentUser-\u0026gt;getRoles(); if(in_array('non_grata', $roles)) { $url = Url::fromUri('internal:/'); $event-\u0026gt;setResponse(new LocalRedirectResponse($url-\u0026gt;toString())); } } } 5- Read More Here you can find much more information about the concepts previously exposed and consult a multitude of use cases and examples:\n DOC: Use of events in the Rules module. DOC: Subscribe to and dispatch events (Event Systems Overview). DOC Event Dispatcher examples in the Symfony documentation. DOC: The HttpKernel in Symfony. DOC: The KernelEvents class in Symfony. DOC: The EventSubscriberInterface from Symfony in Drupal.  6- :wq! Recommended song: Thelonious Monk - Don\u0026rsquo;t blame me   ","permalink":"https://www.therussianlullaby.dev/blog/building-symfony-events-for-drupal/","tags":["PHP","Symfony","Backend","Events","Drupal Development"],"title":"Building Symfony events for Drupal"},{"categories":["Migrations"],"contents":"As I said in the previous post, during these months I will be playing with migrations and preparing a few cases for a future book, I hope. During these days of confinement, I want to keep publishing small articles here to share migration-related experiences.\nIn the previous post, I wrote about Drupal migrations from a toolbox point of view: a set of basic resources to help frame a migration.\nThere is a lot of information to process, and of course there are more concepts, techniques, and tactics involved in solving a migration. So this month I want to write something that lets me play with migrations in a more practical way.\n This article was originally published in https://davidjguru.github.io Picture from Unsplash, user Émile Séguin, @emileseguin\n Table of Contents\n1- Introduction 2- Arrangements 3- Approaches 4- Migrations 5- Key Concepts 6- Resources 7- :wq!\n 1- Drupal Migrations (I): Basic Resources\n Core Modules for Migrations in Drupal. Contributed Modules for Migrations in Drupal. Contributed Modules for Plugins in Migrations. Contributed Modules Drush - Related. Authors you should know.  2- Drupal Migrations (II): Examples\n Introduction: Migration as code or as configuration. Arrangements: Migrating embedded data and CSV file. Approaches for the Migrations. Executing Migrations. Key Concepts for the Migrations. Resources for the Migrations.  3- Drupal Migrations (III): Migrating from Google Spreadsheet\n Remember ETL processes. Exposing Data Through Google Spreadsheet. Special Properties from the JSON transformation. Characteristics of the Migration. Custom Module for Migration.  4- Drupal Migrations (IV): Debugging Migrations First Part\n Basic Debugging of a Drupal Migration: files, database tables and configuration objects. Average Debugging with Migrate Devel Module.  5- Drupal Migrations (V): Debugging Migrations-II\n Introduction to Xdebug. Xdebug for DDEV. Steps for setting up the IDE. Errors launching Drush in containers.   1- Introduction The Drupal Migration API can be one of the most interesting, but also one of the most complex, since its activities are often related to classes and methods of other Drupal APIs (so it\u0026rsquo;s especially particular when debugging). In any case, and as the amount of concepts can be overwhelming, I think we could practice migration mechanics through a couple of exercises.\nWell, for this article I had proposed to model two different migration processes, under a point of view that could be summarized as \u0026ldquo;primum vivere, deinde philosophari\u0026rdquo; (first you experiment, then you theorize). This is why I have decided to organize it in a particular way:\n  The first thing to say is that the two processes are divided into sections that are common to both and instead of finishing one and starting the next one, both go in parallel (you choose your own adventure).\n  Then, only at the end of this post will you find some key concepts used in this article. First we are going to play with the structures, then we\u0026rsquo;ll understand them.\n  So, in the next steps, we\u0026rsquo;ll work through two specific experiences:\n  Migrating Data from an embedded format (maybe the simplest example of Drupal migrations).\n  Migrating Data from a classic CSV file format (just a little more complex than the previous example).\n  Both of the cases are perhaps the most basic scenarios for a migration, so I recommend this article to anyone who wants to get started with their mechanics, as a practical complement to Drupal migrations.\n2- Arrangements First case: Migrating embedded data For our first case we will need, on the one hand, to enable the Migrate module of the Drupal core, and on the other hand, to download and install a contributed module to be able to manage migrations.\nFrom the different options we have, we are going to choose migrate_run, which we have already mentioned in the previous post and could be interpreted as a light version of migrate_tools (although it\u0026rsquo;s actually a fork of the project): both of which provide drush commands to run migrations, so if you have migrate_tools installed you must uninstall it to avoid collide with migrate_run.\nAs a curious note, the first lesson here is that for running Drupal migrations, neither migrate_plus nor migrate_tools are \u0026ldquo;hard\u0026rdquo; dependencies, that is, we can implement migrations without having these modules enabled in our Drupal installation.\nBy the way I have to say that it\u0026rsquo;s important to know that migrate_run is optimized for Drush 9 and later. If you use Drush 8 you will have to use an adapted version, like the Alpha 4, which was still prepared for Drush 8.\nUsing Composer and Drush:\ncomposer require drupal/migrate_run drush pmu migrate_tools # If you need drush en migrate migrate_run -y drush cr Using Drupal Console:\ncomposer require drupal/migrate_run drupal mou migrate_tools # If you need drupal moi migrate migrate_run And you will see in the path /admin/modules:\nBuilding the resources Now, we\u0026rsquo;re going to create a new custom module for our first Migration:\ncd project/web/modules/custom mkdir migration_basic_module Then, the migration_basic_module.info.yml file with content:\nname: \u0026#39;Migration Basic Module\u0026#39; type: module description: \u0026#39;Just a basic example of basic migration process.\u0026#39; package: \u0026#39;Migrations Examples 2000\u0026#39; core: 8.x dependencies: - drupal:migrate Create the new migration definition file with path: /migration_basic_module/migrations/basic_migration_one.yml.\nIn our new declarative file basic_migration_one.yml, which describes the migration as a list of parameters and values in a static YAML-type file, we will include the embedded data of two nodes for the content type \u0026ldquo;basic page\u0026rdquo; to be migrated, loading only two values:\n A title (a text string). A body (A text based on the ChiquitoIpsum generator*, http://www.chiquitoipsum.com).  *Chiquito de La Calzada was a national figure in the Spanish state, a legendary comedian.\nbasic_migration_one.yml\nid: basic_migration_one label: \u0026#39;Custom Basic Migration 2000\u0026#39; source: plugin: embedded_data data_rows: - unique_id: 1 page_title: \u0026#39;Title for migrated node - One\u0026#39; page_content: \u0026#39;Lorem fistrum mamaar se calle ustée tiene musho pelo.\u0026#39; - unique_id: 2 page_title: \u0026#39;Title for migrated node - Two\u0026#39; page_content: \u0026#39;Se calle ustée caballo blanco caballo negroorl.\u0026#39; ids: unique_id: type: integer process: title: article_title body: article_content destination: plugin: \u0026#39;entity:node\u0026#39; default_bundle: page And this will be the structure of the new custom module for basic migration example:\n/project/web/modules/custom/ \\__migration_basic_module/ \\__migration_basic_module.info.yml \\__migrations/ \\__basic_migration_one.yml Enabling all the required modules using Drush:\ndrush pm:enable -y migrate migrate_run migration_basic_module drush cr Or using Drupal Console:\ndrupal moi migrate migrate_run migration_basic_module Second Case: Migrating from csv files For this second case we are going to deactivate migrate_run (if applicable) and activate the superset of modules: migrate, migrate_plus and migrate_tools. Besides, for the treatment of CSV files we are going to use a Source Plugin stored in a contrib module called Migrate Source CSV migrate_source_csv. This contrib module in its version 3.x is using league/csv for processing CSV files. Ok, let\u0026rsquo;s go. So using Composer + Drush:\ncomposer require drupal/migrate_plus drupal/migrate_tools drupal/migrate_source_csv drush pmu migrate_run # If you need drush en migrate migrate_plus migrate_tools migrate_source_csv -y drush cr So, now in the path /admin/modules/:\nBuilding the resources We\u0026rsquo;re going to create another new custom module for our second Migration:\ncd project/web/modules/custom mkdir migration_csv_module With a new migration_csv_module.info.yml file:\nname: \u0026#39;Migration CSV Module\u0026#39; type: module description: \u0026#39;Just a basic example of basic migration process with a CSV source.\u0026#39; package: \u0026#39;Migrations Examples 2000\u0026#39; core: 8.x dependencies: - drupal:migrate - drupal:migrate_tools - drupal:migrate_plus In this example we\u0026rsquo;re going to require a declarative file of the migration too (as in the previous case) but with the exception that we\u0026rsquo;re going to locate it in a different place. This will be placed in the /migration_csv_module/config/install/ path.\nThe structure will look like this just now:\n/project/web/modules/custom/ \\__migration_csv_module/ \\__migration_csv_module.info.yml \\__csv/ \\_migration_csv_articles.csv \\__config/ \\__install/ \\__migrate_plus.migration.article_csv_import.yml So we need a csv with original data to migrate. It\u0026rsquo;s easy to solve this using web tools like Mockaroo, a pretty good random data generator. I\u0026rsquo;ve created a CSV file with some fields like: id, title, body, tags, image. Download it from here. This file will be our datasource for the Migration process. Ok, by now create the directories for the module and put the new custom CSV in the /csv path:\nAnd now, our migrate_plus.migration.article_csv_import.yml file (In later sections we will explain its construction and sections):\nuuid: 1bcec3e7-0a49-4473-87a2-6dca09b91aba langcode: en status: true dependencies: { } id: article_csv_import label: \u0026#39;Migrating articles\u0026#39; source: plugin: csv path: modules/custom/migration_csv_module/csv/migration_csv_articles.csv delimiter: \u0026#39;,\u0026#39; enclosure: \u0026#39;\u0026#34;\u0026#39; header_offset: 0 ids: - id fields: - name: id label: \u0026#39;Unique Id\u0026#39; - name: title label: Title - name: body label: \u0026#39;Post Body\u0026#39; - name: tags label: \u0026#39;Taxonomy Tag\u0026#39; - name: image label: \u0026#39;Image Field\u0026#39; process: title: title body: body tags: field_tags image: field_image type: plugin: default_value default_value: article destination: plugin: \u0026#39;entity:node\u0026#39; Okay, we now have all the resources we need to create our new migration. Now let\u0026rsquo;s see how we approach the process.\n3- Approaches We\u0026rsquo;re going to describe the different approaches that we will apply to our example cases, to understand them better.\nFirst case: Migrating embedded data In this first case, we considered making the lightest possible case of migration in Drupal: Only two nodes with two basic fields each under an embedded format: the lightest possible.\nAlso, in this example we are going to use for the three ETL phases of the migration (Extract, Transformation and Loading) processing plugins already provided by Drupal (we will not develop any custom plugin). If you don\u0026rsquo;t know anything about the concept of Migration Plugins, please stop by for a moment and back here to read a little introduction to the topic.\nTo make things lighter, we will keep the \u0026ldquo;lite\u0026rdquo; version of Migration Tools, Migrate Run. Besides, we will only use the basic commands without any other options or complementary parameters, only with the basic argument of the migration file identifier.\nSecond Case: Migrating from csv files For this execution, I would like to play with something pretty interesting\u0026hellip;due to we\u0026rsquo;ll running this second migration example as configuration, I was thinking that will be funny do the inverse road\u0026hellip;Yes, I propose not to install (activate, drush enable) the new custom module for CSV and leave it\u0026hellip;only as storage for the CSV file.\nLet\u0026rsquo;s move and run the migration from somewhere else. Surprise. Visit the path /admin/config/development/configuration/single/import into your Drupal installation and we\u0026rsquo;ll see there!.\n4- Migrations First case: Migrating embedded data Getting info about the available migrations drush migrate:status drush ms Output from console: ----------------- -------- ------- ---------- ------------- --------------------- Migration ID Status Total Imported Unprocessed Last Imported ----------------- -------- ------- ---------- ------------- --------------------- basic_migration_one Idle 2 0 2 ----------------- -------- ------- ---------- ------------- --------------------- Running migrations drush migrate:import basic_migration_one drush mi basic_migration_one Output from console: ----------------- -------- ------- ---------- ------------- ------------------- Migration ID Status Total Imported Unprocessed Last Imported ----------------- -------- ------- ---------- ------------- ------------------- basic_migration_one Idle 2 2 (100%) 0 2020-03-17 23:19:36 ----------------- -------- ------- ---------- ------------- ------------------- And so, going to the path /admin/content you\u0026rsquo;ll see the two new nodes:\nRollbacking migrations (undoing) drush migrate:rollback basic_migration_one drush mr basic_migration_one Output from console: [notice] Rolled back 2 items - done with 'basic_migration_one' Second Case: Migrating from csv files Well, now in the path /admin/config/development/configuration/single/import we have to import our new custom migration definition file, Ok?\nLoading the migration config data Just go to Import -\u0026gt; Single Item, select the configuration type as \u0026ldquo;Migration\u0026rdquo; and paste the content of the original migration file:\nClick The \u0026ldquo;Import\u0026rdquo; button and the new Config object will be created in the Config System.\nAnd now?\nRunning the migration With the Migration file under the Config management, you can run the process with the same tools as in the former case. Now, we have available a new migration that we can run from console: drush migrate:status\nNow you can execute the migration with: drush migrate-import article_csv_import And all the new nodes will be created. The limit? well, tags and image not will be migrated, cause tag is an entity reference and image is not a link, is a file, and both types must use some differents Plugins\u0026hellip;but we\u0026rsquo;ll talk about this in future posts.\nDrush cex / Drush cim With the migration under the config system, now you can edit, import and export the migration using the basic resources from Drush. For example, testing drush cex:\nAs you can see, the Config System has directly put the new migration file under the management of Migrate Plus and It has performed some actions, such as: renamed the file by placing migrate_plus.migration as a prefix in the file name or added a new file for group (only a way to group migration processes).\nRemember the name of the file? It\u0026rsquo;s just the same that we were using in the /config/install directory, the so-called migrate_plus.migration.article_csv_import.yml. We\u0026rsquo;ve done exactly the same process, but from a different direction. Are you impressed? No? Do you find it interesting?\nRemember also that with this config file, you can use drush cim and load the migration in any other Drupal (with access to the CSV file as datasource, indeed).\nThus we have migrated some 102 new nodes using two different approaches and different methodologies. Not bad.\n5- Key Concepts Migration Plugins Ok, It\u0026rsquo;s very important so we have to repeat one more time the same song\u0026hellip;You must to know the Plugin Format and the diverse world of the existing Migration Plugins.\nEvery Plugin points to a specific data type, a specific format or a different source. You should know the main ones very well and also investigate those you may need, since in migrations they are used extensively. Because of this, for example, we have not been able to migrate taxonomy terms or images in the second case from the CSV file as datasource.\nLet\u0026rsquo;s see the Plugins involved in these two migrations, watching its descriptive files:\nBasic Embedded Migration source: plugin: embedded_data data_rows: ... process: title: creative_title body: engaging_content destination: plugin: 'entity:node' default_bundle: page We\u0026rsquo;re using for extract data from the source the Embedded Data Plugin, a PHP class available in /web/core/modules/migrate/src/Plugin/migrate/source/EmbeddedDataSource.php where in its annotations block you can see some configuration keys that you can use in your migrate file:\n * * Available configuration keys * - data_rows: The source data array. * - ids: The unique ID field of the data. * And data_rows and ids are the keys that we\u0026rsquo;re using in our migration description file. Read more about the EmbeddedDataSource class in Drupal.org API.\nNow, watching the process block and looking for\u0026hellip;where\u0026rsquo;s the Processing Plugin? Well I think this might be interesting\u0026hellip;usually, all the field mappings in a processing block requires a process plugin for each. Then, with some of \u0026ldquo;sintactic sugar\u0026rdquo;, the Migrate API offers a way to reduce and simplify this: if no specific treatment is required for each field, then a single Plugin can take care of all the processing. This \u0026ldquo;default\u0026rdquo; Plugin may also be implicit, so that in the absence of a declaration, the Drupal Migrate API will always apply the same Processing Plugin by default.\nThis \u0026ldquo;implicit\u0026rdquo; and by-default Plugin is the Get class and is provided as the basic solution in processing fields. You can find the Get class in the path /web/core/modules/migrate/src/Plugin/migrate/process/Get.php. Read more info about the Get.php class in Drupal.org API. So actually, what we are saying in a complementary way is that is the same thing write:\nprocess: title: page_title as this other:\nprocess: title: plugin: get source: page_title And so life is a little simpler, isn\u0026rsquo;t it? Remember: in the absence of a processing plugin declaration for a field, Drupal will apply the \u0026ldquo;Get\u0026rdquo; plugin by default.\nOk and finally, for destination we\u0026rsquo;re using the Entity General Plugin with param \u0026ldquo;node\u0026rdquo;, to create diverse elements with node type and for bundles \u0026ldquo;page\u0026rdquo;. This calls to the Destinatio Plugin Entity.php, abstract class in path: web/core/modules/migrate/src/Plugin/migrate/destination/Entity.php and get its own derivative Plugin. Read more about derivative Plugins in Drupal and read about the Entity.php destination Plugin or the derivative migration class.\nCSV datasource Migration I think that the review of the plugins in this case could be easier and more intuitive.\nsource: plugin: csv ... process: ... type: plugin: default_value default_value: article destination: plugin: 'entity:node' For the source, the CSV Plugin, from the migrate_source_csv contrib module. For processing, by default is using Get and for type the Default Value Plugin. For destination, the same plugin as the previous migration: new entities.\nMigration as code or as configuration As you could see, we have treated each migration process differently. The first process (Embedded Data) has been treated as part of the \u0026ldquo;code\u0026rdquo;, without any further particularities. But the second process has been treated as a configuration element of the system itself, making it part of the config/install path, which will create a new configuration object from the installation.\nIn both cases you write the migration definition in a YAML format and then you put the migration file in a place or another. But there are more differences\u0026hellip;Let\u0026rsquo;s make a little summary of these keys:\n  Migration \u0026ldquo;as code\u0026rdquo; is provided out of the box, but the module \u0026ldquo;Migrate Plus\u0026rdquo; allows you treating the file as a configuration object.\n  Depending on which approach you use, the location of the files and the workflow will differ:\n  As code, to make changes to the migration definition you\u0026rsquo;ll need access to the file system and manage the migration file as a code file, something developers-oriented.\n  As configuration, you\u0026rsquo;ll can do changes to the migration definition file using the config sync interface in Drupal, path: /admin/config/development/configuration, in addition to being able to use configuration export/import tools: drush cex, drush cim, cause now you sync the migration (the migration file will be saved in database). This means that you can write, modify, and execute migrations using the user interface. Big surprise.\n  As a configuration object, now your migration file will be create a new configuration registry in your Drupal Config System, and keep it alive also when your migrate module will be disabled. To avoid this and delete the config, put your own custom module as a new dependency of the migration in your migration description file.yml, so the migration will be deleted from Drupal\u0026rsquo;s Active Config just in this moment:\n    dependencies: enforced: module: - my_own_migration_custom_module  Another change is that now, in a config-way, your migration file needs a UUID, just a global identifier for the Drupal Config Management System. Add at first line an unique and custom UUID for your file, to facilitate the processing of the configuration. Remember: UUID is a string of 32 hexadecimal digits in blocks of 5 groups using the pattern: 8-4-4-4-12. Make your own! uuid: cacafuti-1a23-2b45-3c67-4d567890a1b2.   6- Resources Download, play and test the different resources using along this post. I uploaded to GitHub ready to use.\n  Basic Migration File, basic_migration_one.yml, available in GitHub as Gist.\n  CSV Migration File, article_csv import.yml, available in GitHub as Gist.\n  CSV Source File with random data, Gist in GitHub.\n  Codebase of the two migration modules (basic and csv), Available in GitHub. This will be a central repository for all the modules of this series of posts about Migrations, so get the direct link to these two examples:\n Basic Migration one with embedded data. Basic Migration two with CSV file as datasource.    In parallel to this series of articles I\u0026rsquo;m also publishing a series of snippets in Gitlab under the topic \u0026ldquo;Migrations\u0026rdquo;, with a more simplified format, less verbose. Here you can access to the first snippet and get links to the rest of the series. Drupal Migrations Tips (I): Creating a new basic migration structure.\n  7- :wq! Recommended song   ","permalink":"https://www.therussianlullaby.dev/blog/drupal-migrations-two-examples/","tags":["Drupal Migrations","Migrate API","Contrib Modules","Drupal Plugins","ETL"],"title":"Drupal Migrations (II): Examples"},{"categories":["Migrations"],"contents":"I am working on notes for a draft that should become a book about migration processes built with Drupal and its Migrate API. It is expected to be released in June 2020, and collecting, testing, and shaping the content has become quite a large piece of work.\nThere are still a few months left before launch, so, to preserve a little mental health and give these tasks some partial meaning, I thought I could publish a few small posts here, based on those working notes.\nThat way I can share something useful alongside the complementary notes and, if COVID-19 gets me before the book comes out, at least I will have shared something first (I guess).\nWell, what do I want to talk about in this post? I would like to make a list of Drupal modules related to migration processes, available as contrib modules that can add functionality to a migration. This article is only a lightweight set of basic resources (I swear).\n This article was originally published in https://davidjguru.github.io Picture from Unsplash, user Nils Nedel, @nilsnedel\n Table of Contents\n1- Introduction 2- Basic Resources - Core Modules 3- Other Basic Resources - Contrib Modules 4- Extra Resources - Contrib Modules for Plugins 5- Migration Runners - Contrib Modules Drush-Related 6- Authors you should know 7- :wq!\n This article is part of a series of posts about Drupal Migrations:\n1- Drupal Migrations (I): Basic Resources\n Core Modules for Migrations in Drupal. Contributed Modules for Migrations in Drupal. Contributed Modules for Plugins in Migrations. Contributed Modules Drush - Related. Authors you should know.  2- Drupal Migrations (II): Examples\n Introduction: Migration as code or as configuration. Arrangements: Migrating embedded data and CSV file. Approaches for the Migrations. Executing Migrations. Key Concepts for the Migrations. Resources for the Migrations.  3- Drupal Migrations (III): Migrating from Google Spreadsheet\n Remember ETL processes. Exposing Data Through Google Spreadsheet. Special Properties from the JSON transformation. Characteristics of the Migration. Custom Module for Migration.  4- Drupal Migrations (IV): Debugging Migrations First Part\n Basic Debugging of a Drupal Migration: files, database tables and configuration objects. Average Debugging with Migrate Devel Module.  5- Drupal Migrations (V): Debugging Migrations-II\n Introduction to Xdebug. Xdebug for DDEV. Steps for setting up the IDE. Errors launching Drush in containers.   1- Introduction It is not easy to talk about migrations in general and, of course, it is not easy in the context of Drupal either. To run migrations, you need solid knowledge of the technology, the data models at both the source and destination, some experience with ETL processes, and a certain amount of Drupal plugin know-how. Migrations make heavy use of Drupal-style plugins.\nIn any case, since the topic is extensive and my time is now short, I thought of this article as a summary catalogue (for quick consumption) of tools and basic resources for working with migrations.\n2- Basic Resources - Core Modules   Migrate: The Mainframe for migrations, the migrate, that provides the base API for migrating in Drupal.\n  Migrate Drupal: migrate_drupal. Focused on upgrades from Drupal 6 or Drupal 7. Allow reading of configuration entities in Drupal 8.\n  Migrate Drupal: Multilingual migrate_drupal_multilingual. Experimental module in core (¿?). See: https://www.drupal.org/node/2959712.\n  Migrate Drupal UI: migrate_drupal_ui. Interface for upgrading.\n  3- Other Basic Resources - Contrib Modules   Migrate Plus: [migrate_plus](https://www.drupal .org/project/migrate_plus). Migrate Plus is an essential contrib module which extends the features and capabilities of the Migrate core module with a lot of plugins and extensions.\n  Migrate Tools: migrate_tools. Another essential resource: provides a lot of Drush commands for running and managing Migrations.\n  Migrate Status: migrate_status. This little contrib module lets you get a feedback about a migration process. Do you need to know if a migration is running? this module gives you a service that you can call to check the migration.\n  Migrate Files: migrate_files. It\u0026rsquo;s such an interesting set of process plugins that you will want to move files and images with it.\n  Migrate Commerce: commerce_migrate. General-purpose framework that extends to the main Migrate module from Drupal Core, for moving data in a Drupal Commerce scenario.\n  4- Extra Resources - Contrib Modules for Plugins In Drupal migration processes, we\u0026rsquo;ll use different resources to process the ETL migration plan. One of these basic resources, as I mentioned in the introduction, is Drupal Plugins, which requires some knowledge and practice. In a migration scenario, plugins help us read information from the E: Source (Source Plugins), perform the T: Processing (Process Plugins), and save data in the L: Destination (Destination Plugins). Putting these three parts together usually gives us a working migration process.\nMany modules of the core already bring their own Plugins to facilitate migration processes (as the user module). So let\u0026rsquo;s review some migration plugins packaged in contributed modules.\nSource Plugins Migrate Source Plugin   Migrate Source CSV: migrate_source_csv. Contrib Module for migrating data to Drupal 8 from a classic, simple CSV file.\n  Migrate Source SQL: custom_sql_migrate_source_plugin. As a peculiarity of database plugins, this module lets you include SQL queries directly in the .yml file that describes the migration. Those queries will be executed against the source database.\n  Migrate Source YAML: migrate_source_yaml. It\u0026rsquo;s just a simple tool for migrating content from YAML files.\n  Processing Plugins Migrate Process Plugins   Migrate Process Geofield: [geofield](https://www.drupal .org/project/geofield). The contrib Geofield module comes with a custom process plugin for migrations. See Process Plugin in Geofield.\n  Migrate Process XML: migrate_process_xml. Provides process plugins for xpath and xvalue.\n  Migrate HTML to Paragraphs: migrate_html_to_paragraphs. Helps to transform HTML from a migration Source in a Paragraph item (managed by a Destination Plugin).\n  Destination Plugins: Migrate Destination Plugins \u0026amp; Examples What kind of Drupal entities will be created in the migrating process? content entities? configuration entities? Take a look.\n  Migrate Destination CSV: d8migrate. It\u0026rsquo;s a light custom module created by @jonathanfranks.\n  Migrate Destination Config: Class Config.php. Offers a plugin for config migration.\n  Migrate Destination Block: Class EntityBlock.php. Just like and example about the resources that every element can offers in a migration scene, in case of moving Block Entities (are Config Entities) see the PHP classes included in its own module for migrating (Source, Process and Destination).\n  5- Migrations Runners - Contrib Modules Drush-Related   Migrate Scheduler: migrate_scheduler. This module offers integration with the Drupal Cron API to execute migrations under predefined schedules.\n  Migrate Run: migrate_run. Drush commands for running migrations in a lightweight mode. More lean than Migrate Tools but not offer support for migrations groups. It also doesn\u0026rsquo;t depend on the Migrate Plus module. It\u0026rsquo;s just like a little maverick.\n  Migrate Devel: migrate_devel. Provides Drush options to show debug info while executing migrations. Also provides the \u0026lsquo;debug\u0026rsquo; process plugin. Here you can see an article about it. By the way, the Drush 9 compatibility is still unresolved, but a patch seems to be available.\n  6- Authors you should know  Mauricio Dinarte: Mauricio is a developer, consultant, trainer and owner of his own business https://agaric.coop, wrote what is probably the mandatory reading guide for all people who want to learn how to migrate on Drupal: 31 Days of Drupal Migrations, a set of 31 articles published in https://understanddrupal.com/migrations with the most important aspects\u0026hellip;examples, exercises, descriptions\u0026hellip;to understand the whole internal world of migrations inside Drupal.  An essential training material. In addition, his company\u0026rsquo;s website, under the tag \u0026ldquo;migrate\u0026rdquo; also hosts many very good articles about migration topics: https://agaric.coop/tags/migrate.\nSome examples from Mauricio Dinarte:\n Introduction to paragraphs migrations in Drupal:   https://agaric.coop/blog/introduction-paragraphs-migrations-drupal.  Using migration groups to share configuration among Drupal migrations:   https://agaric.coop/blog/using-migration-groups-share-configuration-among-drupal-migrations  What is the difference between migration tags and migration groups in Drupal?   https://agaric.coop/blog/what-difference-between-migration-tags-and-migration-groups-drupal.  His profile in Drupal.org: https://www.drupal.org/u/dinarcon.\n   Tess Flynn: I heard about Tess Flynn reading articles by Mauricio Dinarte. That\u0026rsquo;s how I met this expert developer, speaker and communicator of the Drupal community. On her website https://deninet.com I found content of a different nature, but above all, a series of very interesting articles about migrations under the tag \u0026ldquo;drupal-migration\u0026rdquo;: https://deninet.com/tag/drupal-migration.\nAlong the way I also discovered that it has several contrib modules related to Migrations and Processing Plugins.\n  Some examples from Tess Flynn:\n Migrate Process URL: Provides Process Plugin to migrate link fields.   https://www.drupal.org/project/migrate_process_url.  Migrate Process Vardump: Helping to debugging migrations.   https://www.drupal.org/project/migrate_process_vardump.  Many Process Plugins:   Migrate Process Array, Migrate Process Skip, Migrate Process Trim.  Her profile in Drupal.org: https://www.drupal.org/u/socketwench.\n  Danny Sipos: Author of a well-considered Bible of Drupal -Drupal 8 Module Development, nowadays in its second edition- and writing also in his website https://www.webomelette.com/. Lecturer, trainer and regular attendee at various international Drupal events, has also written some interesting articles about Drupal migrations that you should read.  Some examples from Danny Sipos:\n Your first Drupal 8 Migration:   https://www.sitepoint.com/your-first-drupal-8-migration.  Dynamic migrations using \u0026ldquo;templates\u0026rdquo; in Drupal 8:   https://www.webomelette.com/dynamic-migrations-using-templates-drupal-8.  Quickly generate the headers for the CSV migrate source plugin using Drush:   https://www.webomelette.com/quickly-generate-headers-csv-migrate-source-plugin-using-drush.  His profile in Drupal.org: https://www.drupal.org/u/upchuk.\n 7- :wq! Recommended song   ","permalink":"https://www.therussianlullaby.dev/blog/drupal-migrations-one-basic-resources/","tags":["Drupal Migrations","Migrate API","Contrib Modules","Drupal Plugins","ETL"],"title":"Drupal Migrations (I): Basic Resources"},{"categories":["Reading"],"contents":"Lately, I\u0026rsquo;m interested in improving the tools of the project\u0026rsquo;s stack, thanks to the advice of a friend I approached DDEV. I already knew Docker and everything related to its ecosystem (Docker, Docker-Compose, Docker Engine, Docker Swarm\u0026hellip;) although it is true that not in a very advanced way in general, only at the level of the daily needs or small test games\u0026hellip;so I recently acquired the book \u0026ldquo;Local web development with DDEV explained\u0026rdquo; written by Mike Anello and I\u0026rsquo;d like to share my thoughts on that here. So, Do you develop projects based on Drupal? do you know the DDEV tool for building environments? these may be important motivations, but there are more reasons why Mike Anello\u0026rsquo;s book could be interesting for you\u0026hellip;\n This article was originally published in https://davidjguru.github.io Picture from Unsplash, user Janko Ferlič, @itfeelslikefilm\n Table of Contents\n1- Introduction 2- The Book 3- Recommendations 4- Book Information 5- Fast Review 6- Ratings 7- :wq!\n 1- Introduction As I have already mentioned on other occasions, I was lucky enough to know about DDEV (https://www.ddev.com) from the advice of a friend, Pedro Cambra (@pcambra). He told me about DDEV and he also told me about Randy Fay (@randyfay), a developer of the staff behind the tool. With the first thing (using the tool) I tried a lot and with the second I worked by delegated criteria: if a friend spoke so well about Randy Fay, then I had to pay attention. That\u0026rsquo;s how I started to test DDEV in my environments and projects and I followed Randy.\nThis is important: Ain\u0026rsquo;t no good technology without good people operating behind it and DDEV is a very, very rich project in its community and development. That\u0026rsquo;s why (I guess) the tool and the Human team (here) is very related. Follow them.\nSince I started using DDEV I have had time to learn some things, writing some articles:\n Creating Development Environments for Drupal with DDEV Docker, Docker-Compose and DDEV - Cheatsheet  \u0026hellip;And even attend some sessions about DDEV, like this one at Drupal Camp Spain 2019 by Diego Marrufo (@dimaro_) -in Spanish-:\n https://2019.drupalcamp.es/sessions/docker-para-todos-los-publicos-ddev.html  After all this time, I can tell you that using DDEV has made me happy, and being able to convince teams to align environments with DDEV has made me even happier. Now I am writing a long article about the tool in Spanish (my primary language), and I have been lucky enough to read the book that Michael Anello (@ultimike) has written about the subject.\nAs you can see, I have some relationship with the topic and now I\u0026rsquo;m writing these lines to present and evaluate the book.\n2- The Book The fact that this book has a lot of experience behind it is something you can see, and a lot. Not in vain it is written by Mike Anello, one of the thinking heads behind Drupal Easy, a company with a lot of experience in training processes in general and Drupal in particular. But it is also edited by OSTraining, another organization strongly focused on training, and this is a quite important plus.\nI can say -without fear of being wrong-, that this is the most consistent handbook on technology I have read recently, and by this I mean that any technological proposal should consist of an intuitive sequence:\n Problem / Need that comes to solve. Analysis of the situation. Proposal (technical solution).  Well, this is the sequence that we normally do not find in the anti-teaching of technology. And this is where the construction of this book shines. Here and in the background, and the training background is evident in the fact that this scheme is perfectly fulfilled in this book: we obtain, before anything else, a reference model on the configuration of environments with which we can compare the existence of a functional GAP in our day-to-day life: so we can assess in what moment we are and what sense would have the adoption of the proposed technical solution.\nWe\u0026rsquo;re tired of running after new libraries, versions, etc. Increasing the tooling of our projects, simply chasing the hype, without having clear ideas about what problem we are trying to solve, and this is a very important thing (in fact, the only important thing).\nIt is very gratifying to see that the book devotes an entire chapter to the presentation of the initial problem as a premise (Chapter 2: Introducing Our web development problem), and then continues with the ideal process model (Chapter 3: Professional Development Workflows Explained). After these initial chapters, we will move on to more specific DDEV issues such as:\n Basics of DDEV Installing DDEV (Windows, Mac, Linux) Installing new sites with DDEV (Drupal, WordPress) Cloning existing sites with DDEV (Drupal, WordPress)  And these are only the \u0026ldquo;essential\u0026rdquo; chapters about the tool\u0026hellip;in addition to these, we have other chapters dedicated to equally important issues, such as: DDEV commands, Tips \u0026amp; Tricks, a specific example of Apache Solr-Drupal integration using DDEV containers, sharing the DDEV project with ngrok, or how to configure Xdebug - PHP Storm to work with your DDEV projects.\nAt the end, bringing all pieces together, you will get a complete map of how to approach the project development process (workflow) using DDEV from start to finish, from local to live. If you have doubts or you only know some sections but you don\u0026rsquo;t know the rest, instead of reading N:M articles about the subject, you can have this whole blueprint reading this book.\n3- Recommendations Mainly, this book is indicated for anyone who wants to get introduced to the construction of working environments for WordPress or Drupal.\nIn principle, there are no key prerequisites to enjoy this book, but due to the special emphasis it places on the ideal characteristics of a development environment/team, its reading makes it especially interesting for those people who, due to work circumstances or management conditions, cannot work in the way they might prefer. If you choose to make your team a more productive environment, then it is your book.\nThe book has a very specific target\u0026hellip;\n Do you feel the irremediable existential void when you are solving configuration problems in the project-environment relationship for several days? Well, this book is for you. Do you think you can convince your team of the importance of aligning environments around an easy, simple, agile and intuitive solution? This book is also for you. Do you think you need to provide evidence to an old-school manager to consider more productive the adoption of this kind of tools? Don\u0026rsquo;t hesitate, this is your book.  You can take it in hand and hold it up in front of your managers as if it were Mao\u0026rsquo;s little red book. For us, it will be like that. I assure you.\nAnd remember: \u0026ldquo;Code flows up, Data flows down\u0026rdquo;. Always.\n4- Book Information    Field Description     Title Local web Development with DDEV Explained.   Author Michael Anello.   Publisher OSTraining   Date July 15, 2019   Pages 157   Overview DDEV tool installation and use manual.   Keywords DDEV, WordPress, Drupal, Docker, Development, Environment.   Price 9.99$ // 8.95€ (aprox)   Links Ostraining, Amazon    5- Fast Review    Question // Answer     1- Is it progressive, Iterative and Incremental?   Yes, the book has a simple - complex sequencing.   2- Does it offer specific solutions to particular problems or concrete issues?   Yes, the book is built in a clear problem-solution scheme.   3- Does it explain well the original problems or needs it aims to solve?   Yes, exactly the book begins by setting out the premises of a good environment and the related problems.   4- Is it rich in examples?   Yes, it contains many examples and practical demonstrations.   5- Is it written in plain English, suitable for non-English speakers?   Yes, it’s a very comfortable reading, very simple, very pleasant.   6- Is it up to date?   Yes, it is updated frequently. This edition I have is from July 2019 and I know there is a later edition from August 2019.    6- Ratings 7- :wq! Recommended song: Miles Davis - So What   ","permalink":"https://www.therussianlullaby.dev/blog/books-local-web-development-with-ddev-explained/","tags":["Docker","DDEV","Books","Containers","Drupal Development"],"title":"Books/ Local Web development with DDEV"},{"categories":["Events"],"contents":"I write these lines (or I start them) just as I return from Drupal Day Spain 2019, which this year took place in the city of Zaragoza. I\u0026rsquo;m typing while I recreate in my head various anecdotes, details, conversations and above all, learning.\nIt has been a very enriching meeting and I liked it very much from a technical point of view, but now I want to write mainly about what has been my personal contribution to the encounter: an introduction workshop to the backend of Drupal 8. This year, the event has been organized in the city of Zaragoza, capital of the \u0026ldquo;autonomous community\u0026rdquo; (region with self-government in the Spanish state) of Aragon, in the north of the country. A very beautiful city that in winter accentuates even more its attractiveness: Take a look of Zaragoza in the Flickr albums of the City Council.\n This article was originally published in https://davidjguru.github.io Picture from Unsplash, user Ryan Hafey, @ryanhafey\n Table of Contents\n1- TL-DR 2- Introduction 3- The Drupal .ova 4- Installing VirtualBox 5- Importing Drupal.ova 6- Characteristics, values and parameters (Config) 7- Tools and resources 8- :wq!\n         Group photo of the meeting Drupal Day Spain 2019 - Zaragoza, 23 November,2019.    1- TL-DR 1- Install VirtualBox in your laptop. Download VirtualBox.\n2- Then, download this Virtual Machine and import it from VirtualBox. Download the Virtual Machine \n3- Read the slides from the former folder and follow the steps.\n2- Introduction It seems that for a semester and with some continuity, I will be trying to explain how Drupal works in different places and environments. Due to an accumulation of circumstances, opportunities and total absence of sense of danger, between the last quarter of 2019 and the first quarter of 2020 I will be trying to transmit in the best possible way (those laughs) how Drupal works and how we can approach projects based on Drupal 8.\nWell, in my experience one of the main limitations in a workshop is the time devoted to configurations in general and in particular to some basic alignment of environments, enough so that the fact of giving support in situ eat the time of practice and exercise. So thinking about it I was trying multiplatform solutions, in a pre-configured way and that allowed to assimilate quickly people with little experience in virtualization / containerization, as well as avoid all the tedious parts of common installations - configuration. You\u0026rsquo;ve got two hours for a workshop and you don\u0026rsquo;t want to spend them explaining what Docker is and how to use it.\nBut without much luck: what was agile in deploying on the one hand, greatly limited the customization on the other. What ended up with a Drupal deployed in the web browser, then turned out to be a hell of a question of installations and command-line access. I needed something transversal, preconfigured, agile and with all the initial work already done. So as almost no existing solution convinced me for these purposes, I built my own: I decided to set up my own virtual machine for these activities, a drupal.ova ready to import into VirtualBox and start practicing, with everything you need as standard.\n3- The Drupal.ova In advance:\n Yes, I know Docker and Docker-Compose. Yes, I know how to use Docker and Docker-Compose. Yes, I know pre-cooked Docker-based solutions like Lando or DDEV. This workshop is not for you or me. This workshop is free (freedom). This workshop is for people with different environments (Windows, Linux, Mac), knowledge and experience (including case = zero). This workshop requires no time wasted on system and environment configurations and adjustments. This workshop is for people not involved with Drupal to practice quickly and be motivated to use Drupal in their daily life.  Therefore, I concluded that it was more optimal to use VirtualBox directly. Even if it seems old or vintage or an ugly solution ¯_( ツ )_/¯ . But as long as we are not contradicted by the results of the experience, it is the most agile option.\nWell, the system chosen to align environments in a simple and agile way is hardware virtualization, specifically VirtualBox (https://www.virtualbox.org+), a virtualization tool ready to run the virtual machine I prepared for practice. The deal is this: you install this software on your computer, and I make sure that from there you have a clean Drupal site, ready to practice with.\n4- Installing VirtualBox You will only need to initially download a suitable version of VirtualBox, the one that matches your operating system. You can go here and see the available list, selecting the one that matches your OS: https://www.virtualbox.org/wiki/Downloads. If you already know how it\u0026rsquo;s going, I\u0026rsquo;ll give you links to specific download sections:\n Latest versions of Ubuntu (18.04, 18.10, 19.04): https://download.virtualbox.org/virtualbox/6.0.14/virtualbox-6.0_6.0.14-133895~Ubuntu~bionic_amd64.deb Debian 10: https://download.virtualbox.org/virtualbox/6.0.14/virtualbox-6.0_6.0.14-133895~Debian~buster_amd64.deb Debian 9: https://download.virtualbox.org/virtualbox/6.0.14/virtualbox-6.0_6.0.14-133895~Debian~stretch_amd64.deb Windows, in general, in bulk: https://download.virtualbox.org/virtualbox/6.0.14/VirtualBox-6.0.14-133895-Win.exe And if you have a specific problem or need, here you have installation instructions https://www.virtualbox.org/manual/ch02.html and here you have a user manual (RTFM!) available: https://download.virtualbox.org/virtualbox/6.0.14/UserManual.pdf.  5- Importing Drupal.ova Next, It\u0026rsquo;s time to install the new virtual machine, a heavy file (about 5GB at the time of writing these lines) with .ova extension that once imported from your VirtualBox installation, will create a complete practice environment. You can download it here: https://drive.google.com/drive/folders/1DWTsw0Amzw2f-muGgK5OKo5HIhZZ0RQK?usp=sharing (if at the time of access you see the empty folder, is that must be importing a new version of the virtual machine, wait a little, just a pair of minutes). And once downloaded, import it from the VirtualBox interface.\n        Drupal.ova wallpaper within the Virtual Machine imported from VirtualBox.    6- Characteristics, values and parameters (Config) It has been built with a minimum structure but enough to practice with Drupal, starting with an Ubuntu operating system on which has been mounted a LAMP environment (Linux, Apache, MySQL and PHP) also configured at the level of Apache modules, VirtualHost, database and the installation of Drupal itself.\nSimilarly, if you want to include it in the training purposes, has been installed virtualization software based on Docker: Docker, Docker-Composer and DDEV for the generation of pre-cooked environments Drupal - Docker. You will be able to practice with Docker containers in this Virtual Machine.\nThe operating system consists of an Ubuntu distribution with a limited hardware but able to run it with relative ease. With an allocation of RAM that does not cause too many problems to the host system and that can be assigned in laptops with something of antiquity, as well as the video memory or the hard disk with dynamic reserve. Keyboard and language configured for a target accustomed to Spanish (sorry, the target is people from the spanish state, but you can change this config values) and you\u0026rsquo;ll have the disk of extra features of VirtualBox already installed (you can open a second screen if you have an extra monitor connected, for example).\nIn the next level we have a classic LAMP environment (selected Apache before Nginx for training purposes), with MySQL as database engine, PHP at 7.2 and the three most frequent tools to work with Drupal on a daily basis: Composer, Drush and Drupal Console). You will have access to the project folder called \u0026lsquo;drupal.localhost\u0026rsquo; in the path: /var/www/html/drupal.localhost. And from there you can already use the Drush and Drupal Console commands (they are already registered as aliases from the .bashrc of /home). Likewise, from the web browser you will be able to access Drupal (Apache rises only as a service when the system boots) directly in the address: drupal.localhost. The Drupal 8 website was created through Composer and Drush with the following instructions, so the access data to Drupal will be: admin / admin.\ncomposer create-project drupal-composer/drupal-project:8.x-dev drupal.locahost \\ - stability dev \\ - no-interaction \\ \u0026amp;\u0026amp; cd drupal.localhost \\ \u0026amp;\u0026amp; drush site-install standard \\ --db-url='mysql://drupal_workshop:drupal1$@localhost/testdatabase'\\ --site-name='Drupal Workshop' \\ --account-name=admin \\ --account-pass=admin \\ --account-mail=yourmail@mail.com \\ --locale=en \\ --yes As tools, I have installed the version of VSCode that is compiled without telemetry (VSCodium does not send your usage data to Microsoft) but at the interface and extension level there are no significant differences with VSCode. Also two web browsers, the whole structure to run Docker and Docker Composer and in a complementary way DDEV to work with pre-cooked containers of fast deployment in local environments. Finally, two text editors (Gedit - visual- and Vim -prompt-) and a MySQLWorkbench graphical database client, for the reason of not scaring people who enter Drupal too much- with prompt and commands.\nSystem SO: Ubuntu 18.04.2 — amd64 Kernel version: 5.0.0–32-generic Hard Disk: 13GB (dinámico) RAM: 5.5 GB Video Memory: 64MB Language: Spanish, es_ES Keyboard config: es_ES Ubuntu login: user: drupal, password: drupal1$ Ubuntu machine name: drupal-workshop VirtualBox 6.0.6 Guest Additions for Linux Environment Apache web server: Apache/2.4.29 (Ubuntu) MySQL server: 5.7 Access root: sudo mysql (direct access) Access user: drupal_workshop, password: drupal1$ Login by prompt: mysql -udrupal_workshop -pdrupal1$ Database name: testdatabase PHP 7.2.24 Drupal Core: 8.7.10 Composer (Global): 1.9.1 Drush: Drush Commandline Tool 9.7.1 Drupal Console version 1.9.4 7- Tools and resources Tools VSCodium: version 1.39.2 Firefox — 65.0 Chrome — 78.0.3904.87 Docker — version 19.03.5 Docker-Compose — docker-compose version 1.24.1 DDEV — ddev version v1.11.2 Text Editors: Gedit, VIM Database client (graphic) - MySQLWorkbench 6.3.8 - with a pre-configurated connection to the database - Screen Caption / Image edition: Shutter XDebug zend_extension=xdebug.so xdebug.remote_autostart = 1 xdebug.remote_enable = 1 xdebug.remote_handler = dbgp xdebug.remote_host = 127.0.0.1 xdebug.remote_log = /tmp/xdebug_remote.log xdebug.remote_mode = req xdebug.remote_port = 9000 xdebug.max_nesting_level = 300 xdebug.idekey = VSCODE VSCodium phpcs - 1.0.7 Debugger for Chrome - 4.12.1 PHP DocBlocker - 2.0.1 empty-indent 0.2.0 PHP Debug - 1.13.0 PHP Intelephense - 1.2.3 Composer 0.7.1 Twig Language 2 - 0.9.0 Drupal 8 Snippets - 0.0.2 Drupal 8 JavaScript Snippets - 0.0.2 Drupal 8 Twig Snippets - 1.0.2 Drupal Modules admin_toolbar devel devel_generate kint webprofiler Extra I have given Drupal a \u0026ldquo;HelloWorld\u0026rdquo;-style custom module created in /web/modules/custom, with the name \u0026ldquo;hello_world\u0026rdquo;, already installed, with a routing defined to /hello-world, that is: drupal.localhost/hello-world, where it shows a message \u0026ldquo;Hello World First Route\u0026rdquo;.\nIn a complementary way it has a pair of breakpoints already placed in the answer of that controller, to observe directly the mechanics of debugging in Drupal from VSCodium:\n8- :wq! Recommended song   ","permalink":"https://www.therussianlullaby.dev/blog/drupal-backend-workshop-in-drupal-day-spain-2019/","tags":["Drupal Development","Workshop","Activities","Backend","Community"],"title":"Drupal Backend Workshop in Drupal Day Spain 2019"},{"categories":["Tooling"],"contents":"As I mentioned in a previous month\u0026rsquo;s article, I\u0026rsquo;m still using / playing / practicing with DDEV to build development environments in an agile way. And the truth is that the experience couldn\u0026rsquo;t be more nicer. We know that for some years, working with Docker Engine has become a MUST. From our ability to define specifications(Dockerfiles), configure autobuilds in the cloud to generate images from our repositories(Dockerhub), run images and containers(Docker), connect containers and relate them(Docker-Composer) or deploy container networks( Docker Swarm) \u0026hellip; on this depends the agility that we can give to our daily work. So I\u0026rsquo;ve gathered the most frequent commands in a cheatsheet.\n This article was originally published in https://davidjguru.github.io Picture from Unsplash, user Markus Spiske, @markusspiske\n Table of Contents\n1- Introduction 2- Basics 3- Dockerfile 4- Images 5- Containers 6- Docker-Compose 7- Docker Swarm 8- Others 9- DDEV 10- :wq!\n 1- Introduction Due to the importance of knowing (and practicing) with these processes and daily mechanics, I have thought about gathering the most used commands in my day to day work in the context of Docker.\nAs a Fast-Cheatsheet of work that compiles the most usual instructions of the different parts of the Docker Engine in the day to day, including DDEV, since for me it is already an inseparable part of both: the Docker Universe and the daily work with projects based on Drupal.\nI have assembled everything by specific blocks (Basics, Docker, Dockerfile, Docker-Composer, Images, Containers and last but not least, my new love DDEV. And I also took advantage of it to give it a certain didactic approach: some instructions can be summarized or executed more directly, but I think that many colleagues can learn much more when they understand well the logic under which these tools operate.\nI would have liked to have included many more things, like DDEV hooks, but this was already on the way to an encyclopedia. I promise to write about it soon.\n        Picture from Unsplash, user JESHOOTS.COM @jeshoots    2- Basics # Uninstall old versions of Docker sudo apt-get remove docker docker-ce docker-engine \\ docker.io containerd runc # Installing Docker curl -fsSL https://download.docker.com/linux/ubuntu/gpg \\ | sudo apt-key add - sudo add-apt-repository \\ \u0026quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\u0026quot; sudo apt update sudo apt install -y docker-ce sudo chmod 666 /var/run/docker* # Test if Docker is running or not systemctl is-active docker # List all the Docker CLI commands docker # Get a whole resume about the Docker installation docker info # Get the current version installed of Docker in a fast # and short response docker --version # Get a extended report about the Docker installation # with info about client and server docker version 3- Dockerfile # Mapping ports in a Dockerfile ports: - \u0026quot;8080:80\u0026quot; (Host:Guest) # Mapping volumes in a Dockerfile volumes: - \u0026quot;local_directory:remote_directory\u0026quot; volumes: - type: volume source: /data/mysql target: /var/lib/mysql # Create a tag for a new Dockerfile docker build . -t vendor/name-tag # Remote login to DockerHub docker login # Push the selected Dockerfile tagged to Dockerhub docker push vendor/tag 4- Images # Get a list of existing images docker images // docker image ls # Get all the images but by its ID docker image ls -q # Run an image docker run hello-world # Build image from a certain Dockerfile docker build -t web-custom-name /path/to/dockerfile/ # Run an instance of the former image # and publish port 8080 in container # to the port 8282 on host. docker run -p 8282:8080 web-custom-name # If the image isn't downloaded, then pull it from # remote at DockerHub docker run centos # If only want download an image, without run # a container docker pull ubuntu # Delete images by ID docker rmi $(docker image ls -q) # Remove unused images docker image prune 5- Containers # Run a container but in detached mode and # back to your prompt docker run -d vendorexample/appexample # Run a container with custom name. docker run -d --name web-custom-name nginx:1.14-alpine # Restart a Container docker restart IDCONTAINER # Run a container from the centOS image with bash and # login in prompt docker run -it centos bash # Deploy a mysql database using the mysql image and # name it mysql-db. Set the database password to # use db_pass123. Lookup the mysql image on # Docker Hub and identify the correct environment # variable to use for setting the root password. docker run -d -e MYSQL_ROOT_PASSWORD=db_pass123 --name mysql-db mysql # Run a container in background with a end of life docker run -d centos sleep 100 # Run a container, mapping ports and mapping volumes and # using a user from the container docker run -p 80:8080 -v /locahost/folder:/container/folder \\ -u root jenkins/jenkins # Get a list of existing containers docker ps # List all containers showing it by its ID docker ps -q # Kill all containers running selected by ID docker kill $(docker ps -q) # Remove only a container docker rm IDCONTAINER # Remove all containers with status=exited docker rm $(docker ps -q -f status=exited) # Executes a command inside a running container docker exec idcontainer unixcommand # Connect to the Prompt of a Container docker exec -it IDCONTAINER /bin/bash # Connect to the Prompt of a Container as root docker exec -ti -u root IDCONTAINER /bin/bash # Copying files with Docker # From Local to Remote Docker Container docker cp db/dump.sql IDCONTAINER:/tmp/dump.sql # From Remote Docker Container to local docker cp IDCONTAINER:/tmp/dump_test.sql ./db # Attach local standard output, input and error # streams to a running container docker attach IDCONTAINER # Inspect all the info about a Docker Container docker container inspect IDCONTAINER # Show the log of a container docker logs -f IDCONTAINER # Tailing logs: # Searching for 'error' (case - insensitive) in the # last 1000 log lines of my jenkins (example) # container adding the timestamp at the beginning # of each line. sudo docker logs -t --tail 1000 jenkins 2 \u0026gt;\u0026amp;1 \\ | grep -i error # Describe all the existing Docker Compose Networks docker network ls # Get all the info about a specific network docker network inspect name_network # Delete a network in your Docker Compose system docker network rm name_network # Remove unusued data and clean the Docker System docker system prune -f # Show stats about the running containers. docker stats # Same but with a formatted output. docker stats --all --format \u0026quot;table {{.Container}}\\t{{.CPUPerc}}\\t{{.MemUsage}}\u0026quot; # Fixing results in prompt. docker ps -q | xargs docker stats --no-stream 6- Docker-Compose # Run a multi-container application with Docker Compose docker-compose up -d # Turn on the Docker Compose network # but building images before starting containers docker-compose up --build # Stop a multi-container application with Docker Compose docker-compose stop # Stop your docker-compose network, rebuild it and launch. # Think about creating an alias in your bashrc file ;-) docker-compose stop \u0026amp;\u0026amp; docker-compose up --build --force-recreate # Kill and delete containers based on Docker Compose docker-compose down # Strem the container events for every container # in a project docker-compose events --json # Connect to a container using its alias (no ID, no IP) docker-compose exec ALIAS /bin/bash # Example: executing drush cache rebuild in a Drupal Container docker-compose exec web ./vendor/bin/drush cr # Connecting to a Container (called mysql) as root by prompt docker-compose exec -u root mysql /bin/bash # Get the Docker container's log docker-compose logs -t ALIAS # Get the Docker container's log # with direct connection in real time docker-compose logs -t -f ALIAS 7- Docker Swarm # Docker Swarm: enabling Swarm with the current node. # Swarm is disabled by default. docker swarm init # Docker Swarm: leave this swarm and join another one. docker swarm leave # Docker Swarm: create new service. docker service create my-app # Docker Swarm: create new service with a name. docker service create --name my-service my-app # Docker Swarm: create 3 instances for the service. docker service create --name my-service --replicas 3 my-app # Docker Swarm: Set ports to the application \u0026lt;host_port\u0026gt;:\u0026lt;container_port\u0026gt; docker service create --replicas 3 --name my-service -p 8080:80 my-app # Docker Swarm: Now load a network for the services. docker service create --replicas 3 --name my-service -p 8080:80 --network net-end my-app # Docker Swarm: Updating the service from 3 to six containers. # option 1 docker service scale my-service=6 # option 2 docker service update --replicas=6 my-service # Docker Swarm: Deploy instances of application # across docker host. docker stack deploy -c docker-compose.yml 8- Others # Remove ALL: stopped containers, all networks # not used and all dangling images. docker system prune # Getting a full summary about the Docker resources # in your system, ordered by type. docker system df 9- DDEV If you need an introduction to DDEV, I recommend you read this article that I wrote recently (the previous month): Development environments for Drupal with DDEV.\n        Picture from Unsplash, user Hannah Gibbs, @hannahmgibbs    # Git Clone Project and launch composer install git clone https://github.com/randomuser/my-drupal8 cd my-drupal8 ddev composer install # Initial Project mkdir my-drupal8 cd my-drupal8 ddev config --project-type php --php-version 7.3 ddev composer create drupal-composer/drupal-project:8.x-dev \\ --stability dev --no-interaction ddev config --project-type drupal8 ddev restart # Creating a CMS-specific settings file with # DDEV credentials pre-populated ddev config # DDEV Commons ddev start ddev list ddev describe [project-name] # Using SSH in DDEV Containers ddev ssh # Executing Drush in DDEV Containers from outside the container. ddev exec drush status ddev exec drush cex ddev exec drush site-install ddev exec drush site-install standard \\ --site-name='Drupal Site Install Test' \\ --account-name=admin --account-pass=admin \\ --account-mail=mail@example.com -y # Installing dependencies inside a ddev container. ddev composer require drupal/devel # Install a complete Drupal Site using ddev # in a \u0026quot;single\u0026quot; instruction. mkdir NAMEPROJECT \u0026amp;\u0026amp; cd NAMEPROJECT \\ \u0026amp;\u0026amp; ddev config --project-type php \\ --php-version 7.3 \\ \u0026amp;\u0026amp; ddev composer create drupal-composer/drupal-project:8.x-dev \\ --stability dev --no-interaction \u0026amp;\u0026amp; ddev config \\ --project-type drupal8 \\ \u0026amp;\u0026amp; ddev exec drush site-install standard \\ --site-name='NAMEPROJECT' \\ --account-name=admin \\ --account-pass=admin \\ --account-mail=mail@example.com -y \\ \u0026amp;\u0026amp; ddev start \\ \u0026amp;\u0026amp; sensible-browser http://NAMEPROJECT.ddev.local # Get a list of projects using DDEV ddev list # Describe a project ddev describe project-name # Importing a database file (launch a prompt to set # the location and values of the database dump) ddev import-db # Exporting a database ddev export-db # Importing files assets ddev import-files # Snapshotting your database ddev snapshot // Same as in ddev stop --remove-data # Clean the database and avoid the snapshot ddev stop --remove-data --omit-snapshot 10- :wq! Recommended song   ","permalink":"https://www.therussianlullaby.dev/blog/docker-docker-compose-and-ddev-cheatsheet/","tags":["DDEV","Containers","Docker","Environments","Drupal Development"],"title":"Docker, Docker-Compose and DDEV - Cheatsheet"},{"categories":["Tooling"],"contents":"Well, at this moment we cannot deny that containerization is a basic pillar of our project work: the ability to compartmentalize the technological stack of each project is a fundamental resource (and be wary of those who do not practice it). But the point is that I have a new toy to play with it. Let me explain: I always (or almost always) had worked on Drupal projects with the same stack of versions, so I only had one environment set up. Then, I started to have different versions (Drupal 6.x, Drupal 7 \u0026hellip; 8) at the same time for diverse projects and then I started playing with Docker. But now I have discovered ddev and a new game has started.\n This article was originally published in https://davidjguru.github.io Picture from Unsplash, user Patrick Lindenberg, @heapdump\n Table of Contents\n1- Introduction 2- OS Virtualization 3- Multi-Container applications with Docker 4- And now enter DDEV 5- Setting up a Drupal 8 Site with DDEV 6- :wq! Post-Scriptum - 50 Ways of Install Drupal\n 1- Introduction I read somewhere that there are two main kinds of developers: those who work with a single local stack, which means adapting one main environment for each project, and those who work with an encapsulated environment for every project, using virtualization or containerization tools and accepting one more layer of complexity.\nToday I want to talk about the second option: developers with an isolated environment per project, the tools we can use for that, and of course the role that DDEV has taken in my life (and in my heart). But before this trip into DDEV, we need to remember a few previous concepts.\nLet\u0026rsquo;s go.\n2- OS Virtualization Originally, the problem was: How to host various applications with different versions or diverse stacks in the same single server? and there were three answers:\n Three physical machines. A single physical machine and three virtual machines (Hardware Virtualization). A single physical machine and three containers (Operating System Virtualization).  And the last is the point for us: The Operating System Virtualization, the concept which Docker is based and by extension DDEV as well. Docker was a kind of OS Virtualization based on LXC (Linux Containers).\nLet\u0026rsquo;s see the basic scheme of the Docker Engine Virtualization Model:\nThen, a container (or Docker Container) is a Logical Entity with a copy of the Process Table, the Network Interface and the File System Mount Point, related to the kernel of the OS Host where Docker is running. This is the reason why - unlike in the case of virtualization via hardware where an OS Guest can be different from OS Host - OS-Virtualization must maintain systems that share the same version of the kernel.\nLet\u0026rsquo;s say in summary:\n The kernel of the Host\u0026rsquo;s Operating System is shared across all the containers. Each container is (initially) isolated from others existing containers. Docker virtualize the Operating System of the Host, not the Hardware.  3- Multi-Container applications with Docker We now have a partial deployment performed through the equality 1 Application = 1 Docker container, but now in the next step, we see that a Drupal project = N applications = N Containers. How to relate all this? for that we entered into the use of Docker Compose, a tool to articulate the links and connections of all the Containers that we want to operate in an integrated way within the context of a project.\nNow defining the relationships between containers trought Docker Compose we could deploy applications from some containers.\nConcepts:\n Docker Engine: a set of software resources for work with Docker. Includes a server (Docker Daemon called dockerd, a Rest API and a client Docker CLI). Dockerfile: File with a description about how to build an Docker Image. Docker Image: Implementation of a Dockerfile as a template with the application. Docker Hub: An external (and official) repository with availables Docker Images. https://hub.docker.com/{:target=\u0026rdquo;_blank\u0026rdquo;} Docker Container: A Logical Entity, a running instance of a Docker Image. Docker Compose: A tool for defining and running multi-container Docker applications.  4- And now enter DDEV Well, that\u0026rsquo;s how I was working lately until a good friend of mine (and sensei), Pedro Cambra{:target=\u0026rdquo;_blank\u0026rdquo;} from Cambrico{:target=\u0026rdquo;_blank\u0026rdquo;}, told me about DDEV and asked me to try it. From there everything for me has been joy :-D\nDDEV Website: https://www.drud.com{:target=\u0026rdquo;_blank\u0026rdquo;}\nWhat is DDEV?\nWell, as they explain: Is an open-source, PHP development tool, built upon Docker. It can easliy create local hosting environments, and its server configurations can be version controlled. Originally meant for Drupal development, ddev easily can host Drupal, Wordpress, and GravCMS sites. Since it is based on Docker, ddev is compatible with Windows, Mac, and Linux.\nBut for me, in my own words and in my bad English, DDEV is something like a pre-cooked solution to assemble Drupal projects and later, keep developing with them. Also release as Open Source Software under Apache License v2 in: https://github.com/drud/ddev{:target=\u0026rdquo;_blank\u0026rdquo;} You can see the Documentation here, DDEV Documentation: https://ddev.readthedocs.io/en/stable/{:target=\u0026rdquo;_blank\u0026rdquo;}\nAdvantages\nThere are many operational advantages to using DDEV as a tool for building development environments, but I will summarize some of the main ones:\n Fast Setup: Fast and Simple Setup. Once you have installed all the necesary stack (docker, ddev, etc.), It\u0026rsquo;s extremely easy to initialize a new project based on PHP (Drupal in this case). Routing: There is basic configuration between Containers provided by DDEV. This simplifies all the connections and access tasks between Containers or between user and application. Sharing setups within a Team: You can put the ddev configuration file per project under version control by git. And share it with your workmates. Essentially, you just have to worry about specifying some versions. DDEV is in charge of the rest. And with some more commands, you will not have to do anything more.  My Case\nI will explain myself taking as an example my current situation: At the moment, I have a company laptop with Windows 10, loaded with pre-configured corporate applications that I can not delete and that gives me problems when building a boot manager. So I\u0026rsquo;ve virtualized Ubuntu in virtualbox. In this OS Ubuntu 18.04 I have no resources associated with development\u0026hellip;nothing! I don\u0026rsquo;t have Apache, neither Composer, or Drush, or PHP\u0026hellip;nothing\u0026hellip;only the Docker Engine system mixed with DDEV. Just This. I have everything isolated in Containers, through DDEV. A DDEV structure (of multiple related containers) for each project.\nSo let\u0026rsquo;s see a new iteration on the previous picture to understand what this construction entails for development in a local environment:\n5- Setting up a Drupal 8 Site with DDEV We are going to try to build a development environment and deploy a project based on Drupal 8 thanks to DDEV. Initially, we will start with OS Ubuntu and evidently, these steps should only be completed completely the first time (installation of Docker and DDEV). The following iterations will be much faster with all the tools already installed and configured. Once you have the tools installed, steps 1, 2, 3, 4 and 5 will not be necessary anymore.\nRemember: everything we have seen here in this article is for local development (no production or live).\n5.1- Preparing your sistem sudo apt update sudo apt install -y build-essential apt-transport-https \\ ca-certificates jq curl software-properties-common file 5.2- Installing Docker curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - sudo add-apt-repository \u0026quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable\u0026quot; sudo apt update sudo apt install -y docker-ce sudo chmod 666 /var/run/docker* systemctl is-active docker 5.3- Installing Docker Compose VERSION=$(curl --silent https://api.github.com/repos/docker/compose/releases/latest | jq .name -r) DESTINATION=/usr/local/bin/docker-compose sudo curl -L https://github.com/docker/compose/releases/download/${VERSION}/docker-compose-$(uname -s)-$(uname -m) -o $DESTINATION sudo chmod 755 $DESTINATION docker-compose --version 5.4- Installing Linuxbrew yes | sh -c \u0026quot;$(curl -fsSL https://raw.githubusercontent.com/Linuxbrew/install/master/install.sh)\u0026quot; yes | test -d ~/.linuxbrew \u0026amp;\u0026amp; eval $(~/.linuxbrew/bin/brew shellenv) yes | test -d /home/linuxbrew/.linuxbrew \u0026amp;\u0026amp; eval $(/home/linuxbrew/.linuxbrew/bin/brew shellenv) yes | test -r ~/.bash_profile \u0026amp;\u0026amp; echo \u0026quot;eval \\$($(brew --prefix)/bin/brew shellenv)\u0026quot; \u0026gt;\u0026gt;~/.bash_profile echo \u0026quot;eval \\$($(brew --prefix)/bin/brew shellenv)\u0026quot; \u0026gt;\u0026gt;~/.profile 5.5- Adding the vendor/package to brew \u0026amp; Installing DDEV brew tap drud/ddev \u0026amp;\u0026amp; brew install ddev 5.6- Build a new Drupal project mkdir projectname cd projectname ddev config --project-type php --php-version 7.3 ddev composer create drupal-composer/drupal-project:8.x-dev --stability dev --no-interaction ddev config --project-type drupal8 ddev exec drush site-install standard --site-name=projectname --account-name=admin --account-pass=admin --account-mail=mail@example.com --yes ddev start 5.7- Install and enable some basic Drupal Modules for work ddev composer require drupal/devel drupal/masquerade drupal/admin_toolbar ddev exec drush en devel masquerade admin_toolbar webprofiler ddev exec drush cr sensible-browser http://projectname.ddev.local 5.8- Connect to Container and some commands ddev ssh ddev describe projectname ddev list 6- :wq! What do you think about DDEV and its possibilities? You have to think that many unix commands shown above are totally scrollable to the ddev configuration file like hooks (pre and post formats), but I preferred to make it explicit here as an introduction.\nFinally, I add some links of interest for you:\n Docker Simplified: A Hands-On Guide for Absolute Beginners DDEV, Docksal, and Lando: A Comparison [Docker Tutorial Series : Writing a Dockerfile](Docker Tutorial Series : Writing a Dockerfile) Dockerize Drupal with DDEV  Greetings. :wq! :-*\nPost-Scriptum - 50 Ways of Install Drupal Thinking about putting together some of the various Drupal installation modes, I have created a repository for this. I have called this repository \u0026ldquo;50 ways to install Drupal\u0026rdquo;, mainly for teaching purposes. It\u0026rsquo;s a set of Installers for Drupal, a collection of craft scripts to launch various types of processes installing Drupal codebase. See the repo in gitlab{:target=\u0026rdquo;_blank\u0026rdquo;}\nI have started by building a script that performs all the steps above plus some additions (such as installing npm, Node, Angular and some more packages). It\u0026rsquo;s a script to deploy a local development environment in Drupal - Headless projects with an uncoupled frontend and based on Angular. Angular \u0026amp; Drupal \u0026amp; DDEV{:target=\u0026rdquo;_blank\u0026rdquo;}\nUse: Just for create Debian - based environments to work in Drupal - Headless projects (In this case, using Angular for the frontend). Launch your Terminal (Ctrl + alt + T), go to the folder and launch just: $ . recipe_1_ADD_Angular_Drupal_DDEV\nCharacteristics:\n Search for older versions of Docker and uninstall it. Stop your Apache main server, to free the port 80. Update your package list. Install all the basic resources for Docker Engine in your host system: docker-ce, docker-compose, linuxbrew and ddev. Also install a set of software packages in OS - Host: curl, jq, files, build-essential apt-transport-https ca-certificates software-properties-common Create a new ddev environment with containers for Drupal, database, etc. (See 5 to see the container specifications). Build a new Drupal-based project using ddev, Composer and Drush. Install and enable some basic modules needs to work like devel, webprofiler, admin_toolbar and masquerade. Install some more packages in the container: sqlite3, node, npm and Angular. Open the new Drupal in Browser and goes to the container prompt. Ready to work.  Recommended song   ","permalink":"https://www.therussianlullaby.dev/blog/creating-development-environments-for-drupal-with-ddev/","tags":["DDEV","Containers","Docker","Environments","Drupal Development"],"title":"Creating development environments for Drupal with DDEV"}]