Tip of the day: Standalone DBTNG outside Drupal

Submitted by Frederic Marand on

A few days ago, while I was writing a bit of Silex code and grumbling at Doctrine DBAL's lack of support for a SQL Merge operation, I wondered if it wouldn't be possible to use DBTNG without the rest of Drupal.

Obviously, although DBTNG is described as having been designed for standalone use: DBTNG should be a stand-alone library with no external dependences other than PHP 5.2 and the PDO database library, in actual use, the Github DBTNG repo has seen no commit in the last 3 years, and the D8 version is still not a Drupal 8 "Component" (i.e. decoupled code), but still a plain library with Drupal dependencies. How would it fare on its own ? Let's give it a try...

Bring DBTNG to a non-Drupal Composer project

Since Composer does not support sparse checkouts (yet ?), the simplest way to do bring in DBTNG for this little test is to just import the code manually and declare it to the autoloader manually. Let's start by getting just DBTNG out of the latest Drupal 8 checkout:

# Create a fresh repository to hold the example
mkdir dbtng_example
cd dbtng_example
git init

# Declare a remote
git remote add drupal http://git.drupal.org/project/drupal.git

# Enable sparse checkouts to just checkout DBTNG
git config core.sparsecheckout true

# Declare just what we want to checkout:
# 1. The DBTNG classes
echo core/lib/Drupal/Core/Database >> .git/info/sparse-checkout
# 2. The procedural wrappers, for simplicity in an example
echo core/includes/database.inc >> .git/info/sparse-checkout

# And checkout DBTNG
git pull drupal 8.x

ls -l core/
total 8
drwxrwxr-x 2 marand www-data 4096 févr.  8 09:32 includes
drwxrwxr-x 3 marand www-data 4096 févr.  8 09:32 lib
That's it: DBTNG classes are now available in core/lib/Drupal/Core/Database. We can now build a Composer file with PSR-4 autoloading on that code:
{
  "autoload": {
    "psr-4": {
      "Drupal\\Core\\Database\\": "core/lib/Drupal/Core/Database/"
    }
  },
  "description": "Drupal Database API as a standalone component",
  "license": "GPL-2.0+",
}
We can now build the autoloader:
php composer.phar install

Build the demo app

For this example, we can use the traditional settings.php configuration for DBTNG, say we store it in app/config/settings.php and point to a typical Drupal 8 MySQL single-server database:

<?php
// app/config/settings.php

$databases['default']['default'] = array (
 
'database' => 'somedrupal8db',
 
'username' => 'someuser',
 
'password' => 'somepass',
 
'prefix' => '',
 
'host' => 'localhost',
 
'port' => '',
 
'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql',
 
'driver' => 'mysql',
);
?>

At this point, our dependencies are ready, let's build some Hello, world in app/hellodbtng.php. Since this is just an example, we will just list a table using the DBTNG Select query builder:

<?php
// app/hellodbtng.php

// Bring in the Composer autoloader.
require_once __DIR__ . '/../vendor/autoload.php';

// Bring in the DBTNG procerural wrappers.
// @see https://wiki.php.net/rfc/function_autoloading
require_once __DIR__ . '/../core/includes/database.inc';

// Finally load DBTNG configuration.
require_once __DIR__ . '/config/settings.php';

$columns = array(
 
'collection',
 
'name',
 
'value',
);

// DBTNG FTW !
$result = db_select('key_value', 'kv')
  ->
fields('kv', $columns)
  ->
condition('kv.collection', 'system.schema')
  ->
range(0, 10)
  ->
execute();

foreach (
$result as $v) {
 
$v = (array) $v;
 
$value = print_r(unserialize($v['value']), true);
 
printf("%-32s %-32s %s\n", $v['collection'], $v['name'], $value);
}
?>

Enjoy the query results

php app/hellodbtng.php

system.schema                    block                            8000
system.schema                    breakpoint                       8000
system.schema                    ckeditor                         8000
system.schema                    color                            8000
system.schema                    comment                          8000
system.schema                    config                           8000
system.schema                    contact                          8000
system.schema                    contextual                       8000
system.schema                    custom_block                     8000
system.schema                    datetime                         8000

Going further

In real life:

  • a production project would hopefully not be built like this, by manually extracting files from a repo
  • ... and it would probably not use the procedural wrappers, but wrap DBTNG in a service and pass it configuration using a DIC
  • I seem to remember a discussion in which the full decoupling of DBTNG for D8 was considered but postponed as nice-to-have rather than essential for Drupal 8.0.
  • Which means that a simple integration would probably either
    • use the currently available (but obsolete) pre-7.0 version straight from Github (since that package is not available on Packagist, just declare it directly in composer.json as explained on http://www.craftitonline.com/2012/03/how-to-use-composer-without-packag… ),
    • or (better) do the required work to decouple DBTNG from D8 core and submit a core patch for that decoupled version, and use it from the newly-independent DBTNG Component.

EclipseGc (not verified)

Sat, 2014-02-08 16:59

For what it's worth, leveraging a DIC (Symfony's) looked a little like this: https://gist.github.com/EclipseGc/8885836

As I recall there a few a places we call t() directly in DBTNG's code base as well, and I had to transition those over to Utility/String class usages. Is that not the case still?

Eclipse

Last summer I worked on an alternative comment system (MongoDB-based) for a biggish Pressflow 6 site and, not wanting to code for PF6 in 2013, what I ended up doing was create a small-ish CoreService containing methods for the few unavoidable functions (t() being one of these), and injecting the Drupal core functions to it when running in Pressflow 6 or Drupal 7, and using simpler stub implementations when running unit tests or using the code entirely outside Drupal.

The basic idea, of course, being to develop that code so that a migration to D8 in the future would only require rewriting the tiny "module" part for D8 and let that package basically unchanged.

Come to think of it, this is the core of my "Drupal 678" session at Szeged DevDays: http://szeged2014.drupaldays.org/program/sessions/develop-drupal-8-drup… :-)

I'm replying to that comment since there's no general reply link. I tried this 2+ years later and there are a few minor issues. The URL is now git.drupal.org, composer complained about an extra comma, I changed it to "git pull drupal 8.2.x", and I needed to call Drupal\Core\Database\Database::setMultipleConnectionInfo( $databases ); . Very useful overall.