livetvmatches.com: Under the Hood

Submitted by nigel on Tuesday 29th March 2016
LiveTVMatches.com

My previous blog highlighted my first foray into Drupal 8 development and the successful launch of my site from inception to launch. This blogs takes the narrative further - how I developed the site. 

Underpinning the site is its use of entity content types. The relationships between the content type fields is fundamental to how the data works, and to aid this entity references are now bundled into core whereas back in Drupal 7 there were separate modules for references (for content types) and entity references. So I have a content type for 'Teams' (e.g. 'Liverpool', 'New Zealand'), two of which (home and away) form an 'Event' (along with other fields such as start time and broadcast time) and that 'Event' also needs a 'Competition' reference (e.g. 'Premier League', 'IPL Cricket'). On top of that there is a vocabulary for broadcasters, and further content types to cover the creation of banner type adverts on the site. 

To ensure that my site would gain an advantage over similar sites I needed to ensure that structured schema.org data could be integrated into the pages with RDFa thus improving its SEO ranking. This would be used throughout for the broadcast events that I would listing. Now Drupal 8 supports RDFa and I took a couple of days to asses the contributed module RDF UI thinking this would be a neat way of satisfying my needs. Alas the module doesn't support parent / child relationships and my desire was to use a parent of a Broadcast Event and a child of a Sports Event. So in the end that was abandoned and I manually populated the RDFa in the TWIG templates. 

Each 'Event' uses a convention of '{Home team} v {Away team}' for the title. Since the home and away teams are both references this aught to have been easy to automatically build the title by using the Automatic Nodetitles contrib module. Unfortunately not! The module hasn't been ported to Drupal 8 yet, but by poking around I discovered an early development version and tried it. It didn't work. I then noticed someone had an unmerged pull request that looked like it would provide the functionality I would need, so I manually downloaded the commit and merged it into the code I already had. Hey presto! Automatic Nodetitles on Drupal 8! It's great when a punt comes off! 

When each 'Event' comes around to its broadcast time I wanted an automated Tweet generating and issued to Twitter. This would enable me to gain experience of how to use external PHP libraries within Drupal 8 since I was going to use the reliable PHP TwitterOAuth Library. The first requirement is to have the dependency management tool composer installed on the host box. After that the contrib module Composer Manager should be installed. This will allow Drupal to manage its dedicated composer.json file whilst each package can have its own composer file. Instructions on the use of the contrib module are at Composer Manager for Drupal 8 and I can testify they are well written and easy to follow! With the PHP TwitterOAuth Library available your own class can access it using something like this (vastly abbreviated!):
 

<?php
namespace Drupal\tv_twitter\Model;
 
 
use Abraham\TwitterOAuth\TwitterOAuth;
 
class TwitterModel
{
 
    private $connection;
    private $credentials = array();
    private $friendsids;
    private $followersids;
 
    public function __construct()
    {
        // Setup
        $this->credentials['API_KEY'] = \Drupal::config('twitter.credentials')->get('API_KEY');
        $this->credentials['TWITTER_API_SECRET'] = \Drupal::config('twitter.credentials')->get('TWITTER_API_SECRET');
        $this->credentials['TWITTER_ACCESS_TOKEN'] = \Drupal::config('twitter.credentials')->get('TWITTER_ACCESS_TOKEN');
        $this->credentials['TWITTER_ACCESS_TOKEN_SECRET'] = \Drupal::config('twitter.credentials')->get('TWITTER_ACCESS_TOKEN_SECRET');
        $this->credentials['TWITTER_USER'] = \Drupal::config('twitter.credentials')->get('TWITTER_USER');
    }
 
    public function authenticate() {
        $this->connection = new TwitterOAuth(
            $this->credentials['API_KEY'],
            $this->credentials['TWITTER_API_SECRET'],
            $this->credentials['TWITTER_ACCESS_TOKEN'],
            $this->credentials['TWITTER_ACCESS_TOKEN_SECRET']
        );
    }
?>
Since the Twitter API will be used in an automated way, it was a prime candidate for Drupal Queues and Cron combination so the system checks every five minutes for events about to start, and when so the events are added to the Drupal Queue. A simple mechanism well documented in the Drupal community so not duplicated here. From the code snippet which doesn't include any of the methods that actually do any work, you will see that the class fetches the configuration / credential values for Twitter from somewhere. Back in the days of Drupal 7 settings.php would've been a good place with the $conf variable. That is not how it is done in the new Drupal 8 world. A yml file should hold the values.
modules/custom/my_twitter_module/config/install/twitter.credentials.yml
API_KEY:  'XXXXX'
TWITTER_API_SECRET: 'XXXXXX'
TWITTER_ACCESS_TOKEN: 'XXXXXX'
TWITTER_ACCESS_TOKEN_SECRET: 'XXXXXX'
TWITTER_USER: 'XXXXX'
The next problem I discovered to my horror the xmlsitemap contrib module hasn't been ported to Drupal 8. So I would have to construct my own xml sitemap. There are actually many PHP routines on the Internet to create sitemaps so I found a likely contender and plugged it into hook_cron. Now with every crop of URLs for different sports and different leagues I create I need to coerce a small set of subroutines to ensure sitemap entries are added. It's relatively painless but not as preferable to using xmlsitemap module. Custom modules have also been developed for the daily sports schedules which include the site's landing page, and the blocks for holding the affiliate adverts. Since Drupal 8 uses render arrays in the theme layer I have utilised caching at that level for the ads. The syntax should be familiar to most Drupal devs and appears in the block's class in the build() method.
<?php
    /**
     * {@inheritdoc}
     */
    public function build() {
        $creative = $this->loadBannerContent();
 
        return [
            '#theme' => 'tv_banner_banner_content_bottom_block',
            '#creative' => $creative,
            '#cache' => [
                'max-age' => 300,
            ]
        ];
    }
?>
Something that caught me out initially is programmatic blocks require a dedicated class for each block created - this means a great deal of file copying and repetition although since Drupal 8 requires at least PHP 5.5 a resourceful developer could use Traits for code reuse. Finally I enabled PHP OPcode caching - a look at /admin/reports/status will reveal that it is the recommended code caching for Drupal 8 / PHP. There are a few tutorials online which recommend various settings for your site, but I would recommend the following for a small Drupal 8 build
/etc/php5/fpm/php.ini
;[opcache]
opcache.enable=1
opcache.revalidate_freq=0
opcache.validate_timestamps=0 
opcache.max_accelerated_files=10007
opcache.memory_consumption=192
opcache.interned_strings_buffer=16
opcache.fast_shutdown=1
Hope this has been of help to aspiring Drupal 8 developers. Watch out for my next blog on nginx configuration for Drupal 8 including Microcaching.
blog terms
Drupal Drupal 8 Twitter