Chia sẻ Source demo Drupal 8 - Plugin API Weather forecast

My second session at DrupalCamp in March aimed to provide an introduction to Drupal 8’s Plugin API, illustrated by examples. The plugin system in Drupal 8 provides a powerful way for developers to swap in and out reusable bits of code within modules, reducing the amount of code you need to write to provide versatile, friendly experiences for end users.

Now, as I said in the talk (see below), I don’t generally like the approach to ‘learning’ how to do things by just copying and pasting examples of other people’s code from e.g. StackOverflow, so please treat the samples of code in this post as inspiration, not as the way to do plugins.

Some definitions

Before we get into how to write and use plugins, we need to define some terms.

Entity

If you’ve worked with Drupal at all, you should already be familiar with Entities. An Entity is any object in Drupal, often with fields. Each Entity has an Entity type (e.g. node, comment, taxonomy term, user) of which it is a single instance. It could also be an instance of a Bundle, which is a sub-type of an Entity type (e.g. a single blog post is an instance of a article, which is a Bundle of the node Entity type).

  • Entity type
    • Bundle
      • Entity
        • Field

Drupal 7 introduced the Entity API, which deals with Entities and their properties in a unified way, and brings Drupal closer to an object-oriented methodology.

An Entity is a thing which will always have the same basic shape but which will use different data to produce variations. To use an analogy, an Entity is like a single instance of an origami butterfly. The Entity type is the butterfly shape, and you can create different butterflies by using different types, colours and patterns of paper.

Entities are not easily reusable across projects.

Plugin

A Plugin is a little bit of functionality that can be reused across projects because it provides different ways to produce the same type of thing. Taking the origami example, it’s like making a variety of animal shapes: the thing is an origami animal but there are different ways to go about it. We have our elephant, a lion, a giraffe etc. The result of the process is an animal made of paper but the process is different.

Again, in a kind of object-oriented way, a Plugin is an instance of a Plugin type. Some examples of Plugin types are:

  • Field Widgets (form elements, for field value input)
  • Field Formatters (HTML elements, for field value output)
  • Block (HTML blocks, for miscellaneous uses)
  • Migration Source (different sources to get data from)
  • Migration Destination (different destinations to store data to)

So, Field Widget is a Plugin type, and each type of form element that a field can be rendered to is a different instance of the Field Widget plugin type.

Our challenge: CheapAdvisor

Let’s take a hypothetical example where a client wants a site which is an exact replica of TripAdvisor. The visitor is presented with a list of cities. By choosing a city they can view all the marvelous attractions and events the place can offer.

The client’s budget is £200 and they want it yesterday. Plugin API makes jobs like this much more doable than you might think.

Let’s imagine the customer wants these two home page features to begin with, both of which output information about a particular city:

  • Weather forecast
  • List of restaurants

Of course, it’s possible to imagine many similar features: hotels, things to do, number of inhabitants etc. All of which do the same type of thing: take data about the city and display it for the user.

As part of the requirements, the client has explicitly asked for this extendibility to be easy and documented.

1. Define the Plugin type

The first thing we need to do is define a Plugin type. We’ll call ours ‘CityWidget’.

Each Plugin of the type CityWidget will:

  • Receive a city name as an argument
  • Build widgets with info about the city

The logic it uses to do this will depend on the particular instance.

A Plugin type is predefined by an Interface, like this:

drupal 8

It merely specifies that the Plugin type CityWidget takes a string (the city name) as its input and outputs a Drupal renderable array (city info).

2. Plugin Manager

To load a Plugin, you need a Plugin Manager. This is a service in Drupal 8, which is responsible for defining how to find your Plugins and instantiate them. Basically, it sets the rules for how Plugins can be used. You need to define a discovery method and a factory.

Discovery

  • Finds your CityWidget plugins
  • Finds “Derivatives”, dynamically run-time defined CityWidget plugins
  • Available Methods: Annotation, Yaml
  • Other methods: hook, static, your own

There are different ways to deal with Discovery. The one that we are going to use, and the one most common use in Drupal core, is by annotation, which is a bit of code in the doc block of your Plugin class. Other methods include Yaml, hooks (now only used for legacy reasons),static mapping etc.

You can also write your own Discovery method to satisfy your requirements. For example, if your plugin is some object in AWS or S3, you can easily write a bespoke way for Drupal to find your plugin.

Factory

  • Uses PHP Factory pattern
  • LazyLoad your plugin: From plugin ID To Plugin object
  • Available Methods: DefaultFactory, ContainerFactory, ReflectionFactory

If we have have two CityWidget Plugins, for weather forecast and restaurants, the Factory is where the correct class is loaded and instantiated. This is not something new, the pattern is the standard PHP factory pattern.

It’s LazyLoad, to avoid unnecessary memory consumption, the Plugin is loaded in memory, from the Plugin ID, only when it’s needed.

There are different ways of loading a Plugin through the Factory. The DefaultFactory is the most common one. The one we’re going to use is the ContainerFactory, which is basically a container-aware Plugin, so if you’re confident with dependency injection you can use the create static method to inject any dependency on your Plugin.

Building plugins in practice

There’s been a lot to take on board for a simple introduction so far, so it’s probably time to work through some examples. Luckily, when building our Plugins we have lots of help on hand from Drupal Core.

We can use Drupal Console to generate the skeleton of our Module (remember: Plugins sit within Modules) and check that what we’re trying to do is feasible.

Here I’m using the Console to build the CityWidget project. First I generate the Module (called DrupalCamp), and then the Plugin Type (CityWidget), giving it a machine name. As you see, everything is done for us through the Drupal Console.

As a developer, all that is left for us is to build our two plugins: for the weather forecast and the list of restaurants.

Plugin 1: Forecast

Here’s our first plugin, containing a bit of annotation which defines it. It’s very important that the ID is unique.

drupal 8

We use the interface method to build our output – passing our weather data (e.g. from the weather.com API) through our specified logic.

We also have to pay attention to the folder structure, so that the Plugin is automatically discovered by Drupal. This one lives inside src/Plugin/CityWidget.

Plugin 2: Restaurants

Our second plugin is basically the same, only the names and logic have changed. This one could, for example, hook into the Google Places API.

Again, the plugin sits inside src/Plugin/CityWidget.

Again, the plugin sits inside src/Plugin/CityWidget.

And so, for £200, and about 20 lines of code, we have our CheapAdvisor site!

But wait, there’s more… Derivatives

Now our client comes to us with a ‘brilliant idea’ – they have a list of links where they can place the city name at the end of the URL and get lots more information about the city. We’re sceptical, but the client suggests using iframes to display the external content.

It can be done, but how many plugins will we need to create? If a single plugin lives in a file, are we going to create as many files as there are URLs? What if they need to add more? This is where Derivatives come in handy.

Derivatives allow us to use a single Base Plugin, but define as many plugin instances as we want.

Here’s an example, with an “iframes” Base Plugin that simply generates an iframe with the base URL from the plugin definition (see below how this is populated), and the City URL suffix, normally passed by the Plugin Manager in the same way it does for the Restaurants and WeatherForecast plugins.

The PHP Class on the left is our base CityWidget plugin, the one on the right is our “deriver” for iframes plugins

The PHP Class on the left is our base CityWidget plugin, the one on the right is our “deriver” for iframes plugins. A “deriver” is a description of how the plugin manager needs to instantiate derivatives plugin instances. In our example, it’s how we load all the link URLs our client needs.

These could be listed in a .CSV file uploaded from the client, or provided by a link URl field in a custom Node type.

This is infinitely extensible: the more nodes that are created by the editors (one for each new URL), the more plugins will be available for displaying all the iframes they require.

Note the folder structure: Derivatives live in a separate folder, helping Drupal dynamically load them alongside the Base Plugin.

And finally, here’s our controller for the route /city/{city}

The PHP Class on the left is our base CityWidget plugin, the one on the right is our “deriver” for iframes plugins

It calls our Plugin Manager, which calls Discovery, which finds all the Plugins, and then builds them all in the Factory. For all of them, it passes the city and gives me the widgets, which are then the output for my page.

And in 23 lines of code, we have a pretty powerful (though probably pretty awful-looking) website.

Tags: 
Fivestar: 
Average: 5 (1 vote)