Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough
Jul 24 2018
Jul 24
I had a hard time getting the #default_value to work for the date form field because the examples module and the documentation uses this approach:

    $form['send_date'] = [
      '#type' => 'date',
      '#title' => $this->t("Send Date"),
      '#default_value' => [

        'month' => 2,
        'day' => 15,
        'year' => 2020,
      ],
    ];

After playing around with it for a while, I was able to get it to work by passing in a YYYY-MM-DD value, using date.formatter (the new way to format_date()).

    $date_formatter = \Drupal::service('date.formatter');
    $form['send_date'] = [
      '#type' => 'date',
      '#title' => $this->t("Send Date"),
      '#default_value' => $date_formatter->format(REQUEST_TIME, 'html_date'),
    ];

Looks like this is a known issue that has also caused others lost time. :( Hopefully this will get rolled out soon.

Jun 08 2018
Jun 08

Background

I am the sysadmin and developer for Art & Object, a Drupal 8 website built with the Drupal Composer project. The version pin in composer for Drupal was 8, which in hindsight was too broad for our usage. Meaning Drupal point releases (8.3 to 8.4) require study to ensure you understand all the implications, which wasn't something I did. I just blindly did a composer update, thinking everything would be handled automatically.

This really bit me when 8.4 came out because my server was running Debian Jessie, which runs PHP 5.6 and my composer didn't have a platform PHP configuration, so a lot of the underlying Symfony code updated to PHP 7. So I ended up doing a backgrade until I figured it out.

Then there were the critical security Drupal updates (SA-CORE-2018-002 and SA-CORE-2018-004) earlier this year that would not be released for 8.3, so I had to upgrade (or at least, at the time, I felt I had to, though I see now they have a patch for older 8.x releases). By that time, 8.5 was released, so I updated the composer to 8.5 and ran update and after some basic testing, moved on.

Then a few months later, I noticed the status error messages about running the contrib media module alongside the core and I knew I missed something and there was a problem.

I then started down a wicked rabbit hole of getting a local copy running and following the upgrade instructions, running into problem and going back to getting a local copy running fresh again and trying again. Lots of trail and error (mostly errors) and head-banging-on-the-desk. I looked for help on the #media IRC channel, but the best advice came from posting on Stack Overflow, where @sonfd pointed out that the media module needs to be uninstalled first. I thought I had tried that and ran into an error message that mentioned you can't uninstall the media module with media items already created.

The Fix

So after lots and lots (and lots) of local refreshes and trials and errors, here's the list I finally followed when it came time to upgrade production:
  1. First, put the site in maintenance mode. Then take a database backup and make a tarball of your project directory. Don't skip over this.
  2. drush pmu media crop_media_entity: pmu = pm-uninstall. Remove the media module (and crop_media_entity, if you have that, too). This was the tip from @sonfd that opened the rest of this process for me.
  3. composer remove drupal/media: Remove the contrib media module from the filesystem. I should add that I prefixed all my composer commands with /usr/bin/php -d memory_limit=-1 /usr/local/bin/ because I often ran into memory limits when running composer.
  4. composer require drupal/inline_entity_form drupal/crop:1.x-dev drupal/media_entity_instagram:2.x-dev drupal/media_entity:2.x-dev drupal/media_entity_slideshow:2.x-dev drupal/media_entity_twitter:2.x-dev drupal/slick_media:2.x-dev drupal/media_entity_actions: These modules are temporary to help upgrade the database records.
  5. composer remove drupal/video_embed_field: For some reason, I couldn't require video_embed_field:2.x-dev, so I removed it and then...
  6. composer update: When I ran this, it updated video_embed_field to 2.x-dev.
  7. composer require drupal/media_entity_image drupal/media_entity_document drupal/image_widget_crop: More temporary modules to help the upgrade process.
  8. drush cr: Clear cache to make sure Drupal picks up new modules and paths.
  9. drush updb: Run the database updates.
  10. drush pmu entity media_entity: Uninstall these modules (these were the old contrib modules)
  11. composer remove drupal/media_entity drupal/media_entity_image drupal/media_entity_document drupal/crop drupal/image_widget_crop
    /usr/bin/php -d memory_limit=-1 /usr/local/bin/composer require drupal/crop:2.x-dev drupal/image_widget_crop drupal/empty_page:2
    : Clean out the temporary modules from the filesystem.
  12. drush cr: Clear caches
  13. drush updb: Run database updates
  14. drush cex: Export the configuration (so you can commit it later).
  15. The blazy module had an error with the core media and hasn't been updated (as of this writing), but there is a patch to fix that. So I learned how to add patches to a composer file - turned out pretty simple. Add this to composer.json in the extra section:
            "patches": {
                "drupal/blazy": {
                    "Gets Blazy to work with Drupal Core Media": "https://www.drupal.org/files/issues/2881849-8.patch"
                }
            }
  16. composer update: This was odd, but I had to do an update, which picked up the patch, but didn't really install it. I can't remember exactly now, but I believe this actually deleted the blazy folder.
  17. composer remove drupal/blazy: So removing this actually installed it. Who knew? Whatever ... it's still in my composer.json and now the filesystem has the module and the patch.
  18. drush cr: Clear caches!
  19. For some reason, this upgrade created a new field called field_media_image_1 and assigned that as the source for the image media type, which broke some of the images on the site. So I edited media.type.image.yml file to revert source_field back to my original field_image.
  20. drush cim: Import my hack to get my media image type to work.
  21. I had a custom field formatter that I had to edit to change the namespace from media_entity to media.
  22. drush cr: Final cache clear!
  23. Test and make sure all is well. If so, take the site out of maintenance mode and commit your repo changes.

Advice / Conclusion

A lot of this pain could be negated by studying the release notes better. I own that and this counts as one of my many scars of lessons learned. I hope others can learn from my lesson, too. Someone may end up writing a meta post about this post to point out the high cost of maintaining a Drupal site and I don't think they'd be wrong about that, but that's the price you pay for running servers that are publicly accessible.
Oct 11 2017
Oct 11
I was using a Drupal composer template and a few days ago, it upgraded from 8.3 to 8.4 automatically. I noticed and didn't really think much of it, so I applied the database updates and went on my way. Today, two days afterwards, I ran across this article which pointed out that PHP 7 is required for the underlying Symfony framework. Our site is running on Debian 8, which has PHP 5.6 and powers other PHP applications, so I wasn't looking to update the underlying OS and possibly break my other PHP things.

So I downloaded a db snap from 8.3 and then re-applied the 8.4 update and took another database snapshot. Then I diff'd the database snapshots. The biggest changes seemed to be in the cache tables and removing and adding some revision columns. So I reverse-engineered a backgrade SQL script. With that, I updated the composer.json file from this:
        "drupal/core": "~8.0",

to this:
        "drupal/core": "8.3.*",
Then I took a precautionary database backup and then did a composer update, which took care of the code backgrade. Then I ran my script (drush sqlc < backgrade.sql) and then I did a drush entity-updates to actually update the database schemas to match the backgraded code.

Now I just need to ignore Drupal telling me about 8.4 until I'm ready to fully embrace PHP 7. I feel like something bigger needed to get my attention to the update in system requirements when I ran the initial composer update.

Jan 13 2017
Jan 13
I realize I haven't updated my blog in awhile, because I've been in the thick of developing and rolling out our new site (check it out!). This was taking our existing Drupal 7 site for FSR Magazine and migrating it to this new site, which is responsive and better catered to deal with all food service news. Unfortunately, we ended up killing the Drupal 8 upgrade because it was just too complex, given our custom module codebase. D8 has a mindset change, where everything is an Entity and it takes a lot of OOP and YAML files to get things done.

I figure it would cost us hundreds of hours to upgrade to D8 and it was tempting to upgrade to D8 for the configuration management, Big Pipe, and better caching features. But that's time and money we didn't want to spend on the upgrade.

I've talked with some folks at my local Drupal User Group (shoutout TriDUG!) and several others are in the same boat, where existing sites aren't upgrading, but rather, they're doing new sites in D8.

At some point, we'll need to do something, but we're able to do what we need with D7. If pressed, it may be easier to port to Backdrop vs. upgrade to D8.

Nov 17 2016
Nov 17

This latest release for [RNG](RNG brings two major features: The ability for anonymous users to register for events. And the ability to create, and associate non-users with events.

RNG is an event management module for Drupal 8 created in the spirit of Entity Registration (Drupal 7) and Signup (Drupal 6). Users can create registrations for events, and event managers can manage these registrations.

Note: This post discusses updates to the RNG project which are available in a beta release. See this issue for how to get RNG 1.3 beta.

The event registration form has been reworked into a re-usable Drupal element, whilst making heavy use of AJAX. The registrant selector now accepts multiple registrants. Registrants can also be modified after the registration is created.

*Associate multiple registrants with a registration.* *Create new registrants within the registration form.* *Modify the meta registrant form within the registration form.*

Access control has been reworked to permit anonymous users to register for events.

RNG requires that all registrants for a registration are a Drupal entity. Since anonymous users do not correspond to a user entity, the RNG Contact project provides a way to create non-user registants in a similar fashion to how contacts work on your phone.

See main RNG Contact article: RNG Contact: Anonymous registrants for RNG.

  • Registrant entities now have bundles
  • Added registrant type configuration entity
  • Added control over which identity types can be referenced or created within each event type.
  • Added ability to specify minimum and maximum registrants per registration.
  • Added an interface to view and add RNG related fields.
  • Event settings pages now use the admin theme.
  • And many other behind the scene changes.

Cover photo: B&W Crowd by whoohoo120. License CC BY 2.0

Jun 20 2016
Jun 20

Preface

Wow. Entities. What a mess. I really struggled with this. There's an example of a database-based module not using them and one that does use them. I think it boiled down to me that what I was representing were reports, which seemed very similar to nodes (in hindsight, perhaps this should have been a content type all along), so I should use entities.

Entities

As I look through examples, trying to figure out how to get my menu routing to work with my database entries, it appears that I need to set them up as entities. I say that, looking at the xmlsitemap and dbtng_example and content_entity_example from the examples module. So, let me back up and learn about D8 Entities. It looks like entities were introduced in Drupal 7 and there is a guide for that, but it's based on D7, so the concepts stuff is ok, but the implementation guidance is outdated. There's a link at the top to take you to a D8-specific guide. The guide says it's just a holder, but I'm going to give it a go. If nothing else, perhaps this post will help others. First, entity types. Either you're setting up a configuration entity or a content entity. I think content entity fits for me. Next, looking at requirements section of the content type doc, I need to setup src/Entity/GatedContentEntity.php file with docblock stuff that will give the loader the information it needs. Hey, checkout this helpful hint about double quotes in the annotations doc (post #1 reference)! That annotations doc doesn't provide what all you need, but I'll take those 3 lines to start off with. I think this doc may list all of the properties you can use with ContentEntityType, but not sure which ones I really need and how to use them. This is when the examples/content_entity_example/src/Entity/Contact.php file helped. I first just used the core form & access handlers for now.

Random Aside: Sublime Text 3

I use Sublime Text 3 as my IDE and there is a PHPDoc plugin that makes life editing these annotations easier. Package Manager makes it easy to install plugins: just follow the installation instructions (and restart ST), then follow the usage instructions to pull up the Install Package option and then type phpdoc and select it to install. Then restart ST again and you'll be much happier. I think the same applies for Sublime Text 2, too. This Drupal doc has some other helpful Sublime Text tips.

Back to it...

So I found this great doc on how to create your own content entity. And whoa ... this comment pointed me to the Drupal Console project, which can jumpstart this a LOT. So I tucked my handcrafted code away and ran this command: drupal --target="fnmd8.local" gect --module="gated_content" --entity-class="GatedContent" --entity-name="gated_content" --label="Gated Content" The end result is about 10 files with lots of boilerplate fields that don't match up with what I'm using. Maybe I'm going about this all wrong. Maybe I don't need Entities? Time to take a break...

After the weekend

So I went back and added the preface above and I feel I need to stick the course with Entities. Perhaps this post will be totally rewritten after I totally understand them and and write a better guide that flows better. I need to just release this post because I'm about to flip back to some D7 work and will be taking another break. I'll pick this post thread back up again shortly.

Jun 16 2016
Jun 16
This is the next steps in getting my module working in Drupal 8. In the previous post, I got the block setup working, but it's just a generic "Hello World!" output. The block gets the most recent entries from a database table, so my next step is getting the database setup.

Databases

This doesn't seem to have changed from Drupal 7 to 8. You basically copy over the .install file. The hook_schema() takes care of it. BTW, one thing that is different is if you need to uninstall/reinstall the modules, you just need to drush pm-uninstall MODULE vs. drush dis MODULE ; drush pm-uninstall MODULE.

Once I had the database tables in place, I dumped my database tables from D7 and imported them into D8. NOTE: I probably need to either codify this (perhaps there's a way to hook into the migration system?) or keep notes about what tables to dump/import once everything's in place.

Now I needed to update my block to query the most recent entries in the table. db_query() is still supported in D8, however it looks like it's going away for D9, so if I wanted to future-proof my module even more, I could convert it to the new injection technique. So I created a storage class in my src folder. I downloaded the examples module and looked at the dbtng_example submodule and its DbtngExampleStorage class. I also found this answer on StackOverflow and the example posted in the select doc helpful. I ended up with a hybrid approach:

So a few things there. First, my post_date field was a legacy datetime field, so I had to convert the current time from a timestamp to the date format that mysql expected. format_date() is deprecated, so they recommend using Drupal::service('date.formatter'). Also, I make my __construct() method a little dynamic so that I don't have to pass in a connection, but it can support it if it is passed in.

Then my block build() method uses my storage class and calls the get_reports() method, passing in the block's configuration array. First, I add use Drupal\gated_content\GCStorage; to the top of my block plugin. Then my build() looks like this:

  public function build() {
    $storage = new GCStorage;
    $reports = $storage->get_reports($this->configuration);
    $build = array();
    kint($reports, $build);
    return $build;
  }

Block View

So now that my block has the right set of reports to show, I need to show them. This takes me to the whole theming and templates stuff. The D7 version called theme() directly and there was a hook_theme and I had a template. The end result was a simple
and foreach report, a
and l(); call to link to the report. Kind of reminds me of item_list a bit, so I think I'll start there.

Ok, it looks like item_list looks about the same. My render array just needs to '#theme' => 'item_list'. But l() is gone. This comment pointed me in the right direction. So now it looks like this:

    $items = array();
    foreach ($reports as $report) {
      $url = Url::fromRoute('');
      $items[] = \Drupal::l(t($report['title']), $url);
    }
    $build = array(
      'report_list' => array(
        '#theme' => 'item_list',
        '#items' => $items,
        '#attributes' => array(
          'class' => 'report-block',
        ),
      ),
    );
Success!

Of course, these all link to just the homepage, which isn't very helpful.

Menu

So now I'm looking down another rabbit hole. The URL to my report is reports/[ID#]. But I use this module on separate domains, so the reports part is configurable. So I need to get that configuration setting to get the first bit of the URL. Actually, I think I can skip that since we'll be doing something different in our migration plans, so I can skip that part of the module. [postnote: if I do end up needing this, it looks like this shows how]

Also, I had this module all setup with pathauto to get a nice SEO-friendly public URL. Ok, so I need to setup the menu routing so the user can get to /gated_content and /gated_content/[ID#] and /gated_content/[ID#]/download. And I need to setup pathauto again.

First, all of those URL's were setup in D7 with type of MENU_CALLBACK, so according to this doc, I need to set them up in a .routing.yml file. This wasn't something DMU setup for me, so I did this from scratch. This was a great doc that showed some examples going from D7 to D8.

My first question is that I was familiar with named placeholders and how the name would be used to call a NAME_load() function to put together the argument that gets passed into the callback. But these examples are using book, which uses node and entity, so I need something that will use my own data type.

Stuff for the next post!

Jun 15 2016
Jun 15
We are looking into porting our site to Drupal 8 and we have over 30 custom modules, so it's a pretty big undertaking. I thought I'd blog about the adventures in the hopes of helping others tackle porting to D8 as well as notes for myself.

Back in April, I took one of our sites and used Drupal Upgrade module to get it moved into D8, following these instructions. I really haven't touched it since then, so I can't really remember how that went. So I just blew the dust off and upgraded from 8.0 to 8.1 in the process. One of the first things I noticed is that comments were turned off, so I had to navigate into the various content types and edit them and edit the comments field to change it from Closed to Open.

Then I was looking through our custom modules and trying to figure out where to start first. Some of our stuff builds on top of each other, so I need to look at the hierarchy to determine what is something that isn't dependent on other things.

One of my first steps was to create a custom modules folder and copy my D7 custom module into that. I then copied the folder as a backup. Then I used the Drupal Module Upgrader (DMU) to generate an upgrade info report as well as attempt the upgrade process. Then I renamed that folder to append -auto and then I created a new folder where I will start work in earnest.

cd /path/to/drupal8 ;
mkdir -p modules/custom/gated_content ;
cp -r /path/to/drupal7/sites/all/modules/custom/gated_content modules/custom/gated_content ;
cp -r /path/to/drupal7/sites/all/modules/custom/gated_content modules/custom/gated_content-orig ;
drush dmu-analyze gated_content --path=modules/custom/gated_content ;
drush dmu-upgrade gated_content --path=modules/custom/gated_content ;
mv modules/custom/gated_content modules/custom/gated_content-auto ;
mkdir modules/custom/gated_content ;
So my first custom module handles what we call "gated content," which is a collection of PDF files that our website users can request to download, which takes them to a request form and after they fill it out, it emails them a link to the PDF.

The module also has a block that shows the most recent gated content entries, which we include in our sidebar.

So I just wanted to start with the block and the block's settings.

The Block

One of the reasons I wanted to start from scratch is that this module is pretty big with lots of code that needs addressing to work in D8 and I'd like to incrementally get stuff working without having all the old stuff in there. So I copied the module's .info.yml and .permissions.yml files into my blank folder to start. No .module file. At least for now. Blocks have a different way of doing things. You create a plugin. You don't need to point to your plugin through some .yml file. You create a src/Plugin/Block folder in your module and then create a php file for your plugin class. The filename needs to match PSR-4 specifications, which is the magic that autoloads all of this. So I called mine GCListBlock.php. One of my first lessons is that my namespace has to match the module name. My module name is gated_content and I initially used gatedcontent, which resulted in the block showing up in the list, but once you tried to place it in a region, it would result in an error. Another odd thing is that the phpdoc is very specific with double quotes vs. single quotes. Use double quotes. Using single quotes resulted in this error:
Doctrine\Common\Annotations\AnnotationException: [Syntax Error] Expected PlainValue, got ''' at position 18 in class Drupal\gated_content\Plugin\Block\GCListBlock. in Doctrine\Common\Annotations\AnnotationException::syntaxError() (line 42 of /Users/jason/Sites/devdesktop/fnmd8-dev/docroot/vendor/doctrine/annotations/lib/Doctrine/Common/Annotations/AnnotationException.php). Another hiccup I ran across is I had t() calls through my block code that I had to change to $this->t(). I think there was some sort of 8.1 update, which wasn't reflected in the Blocks doc I was pointed to from the DMU report (I added a comment), that says $form_state is now a FormStateInterface. This also means you need to update accessing the form_state values to use $form_state->getValue('FORMFIELDNAME').

Finally, blocks don't just show up in the list of blocks on the blocks page like you might expect from Drupal 6/7 days. You have to click the Place Block button to pull up a list of available blocks and you should see it there. This is pretty neat because you can add a block more than once to a region or across multiple regions. I'm sure there's some way to differentiate the build based on its own order and region, but I don't have to worry about that for now.

The Settings

I initially thought I needed to create a settings yml file, but actually, since the settings aren't used outside of the block code, they can be all specified and used in the block's class. You can provide the defaultConfiguration() method to specify the default values (return an array of keys to values).

Bonus: Devel Module

One of the hiccups I faced was how to get the devel and dpm() calls working like I was used to in D7. One of the cool things I undercovered while getting devel setup is the webprofiler submodule, which adds a nifty toolbar at the bottom of the page with lots of development information and options.
I couldn't get dpm() (or dsm()) calls to show anything, so I stumbled across the kint submodule, which looks even better. You basically enable it and then use kint() vs. dpm(). One bonus is that kint() allows for multiple variables in the same call. It outputted the variable in a narrow region, but there's right arrow you can click on to open it in a new tab. And there's a useful stack trace beneath the output. One other thing I read is that dpm() doesn't have access to protected data, but kint() does.

Also, if you're not seeing output from either dpm() or kint() calls, try doing a cache-rebuild (drush cr). If that fixes it, you may want to setup local development configuration overrides so you don't have to rebuild manually every time.
Sep 22 2015
Sep 22

RNG has tagged numerous alpha releases and maintained stability with Drupal core. Because of this stability I will be accellerating to 1.0 by skipping beta and going straight to RC. However RC will not occur until the following requirements are satisfied:

Everything should line up for a RNG RC in October or November 2015.

Updated October 20, 2015: All requirements were satisfied, the first RNG release candidate was released on October 14th, 2015.

Cover photo (unmodified): Running gear of steam locomotive by Petar Milošević. License CC BY-SA 3.0

Jun 16 2015
Jun 16

After 5 months of development, RNG is ready for its first alpha release. A milestone where there are no major issues, and schema is not anticipated to change leading up until v1.0.

RNG is a Drupal 8 module implementing a core toolset for allowing users to register for events. An event can be a presentation at a conference, a meetup, a class. If you need a way to associate people with a Drupal entity, take a look at RNG.

Core features of RNG:

  • Any entity type — designate any bundle (e.g node type) as an event type.
  • Self and proxy registration — users can register themselves or other identities to the event.
  • Multiple registrants — registrations can associate multiple identities.
  • Messaging — messages, such as email, can be sent to registrants. Messages have token support, allowing you to recycle field values from the related event, identity, and registration. Messages can be timed, or sent when a registration is created.
  • Additional identities — the Identity module allows you to register non-users. It is modeled like an address book, where contacts are added, and then made available as an option on the registration form.

Theres still a few things that will make it in before 1.0 final. Including improved Views integration, and improved/full test coverage.

I work on RNG in my own time. If you want to help out, don't hesitate in posting in the issue queue or contacting me directly.

Need help getting setup? See Quick Start.

Download the module on drupal.org.

Cover photo: Seedling by sjg. License CC BY-NC 2.0

About Drupal Sun

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

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

See the blog post at Evolving Web

Evolving Web