Drupal Module Development with A Custom, Custom ORM

 

I was looking beyond Drupal’s Entity Framework for a lean Object Relational Mapper to do my Drupal 7 module development; something that permits working with custom tables that are not quite tied into Drupal’s node tables or other node related hooks that may slow things down. I came across this custom ORM from the guys at Phase2 and made it work really well for my needs.

The RootsRecord abstract class basically abstracted Drupal 7’s DB IO rigor and allowed a clean (and lean) OO method of building custom modules to work with custom tables. When I first implemented RootsRecord there were a few bugs and some things that could be done a bit better so I made some tweaks, fixes and adjustments and got it working quite well with a late version of Drupal 7.

Here’s the custom,custom RootsRecord source I currently use:
RootsRecord.class

For module development, I found that there were a couple of minor things that had to be done to allow the ORM wrapper to do its job for CRUD and permit further integration with Drupal such as Views compatibility. I found a couple contrib modules that would be needed to automate this integration, these were:

Drupal’s Schema module:
https://drupal.org/project/schema

Drupal’s Data Module:
https://drupal.org/project/data

Once I set up a fresh copy of my Drupal 7 distro and installed the needed modules namely, Views, CTools, Devel, Schema and Data, I proceeded to create my custom tables within the drupal database. For easy identification I used prefixes for all custom tables as well as ensured that each had a primary key defined (db development 101 but extremely important for RootsRecord to work as well).

Yes, I’m actually using a SQL Server backend with Drupal; for this particular project I needed SSRS and SSIS along with the out-of-the-box and modular capabilities of Drupal’s framework (for fast, solid development) if you’re wondering. If set up correctly, this provides a versatile, powerful and capable solution IMO.

Next, I created custom module folders and related files (it was a pretty large solution). Here’s a look at the directory structure I used:

  • cceis
    • employee
      • objects
        • RootsRecord.class
        • Employee.class
      • employee.info
      • employee.install
      • employee.module

I actually threw the custom, custom RootsRecord.class into a central, prerequisite ‘datalink’ module that handled some other custom bits and allowed all other dependent modules to share the same RootsRecord.class but for the purpose of this example, I’ve simplified it by throwing it local to this module under the /objects folder. I also, preemptively created a blank file ‘Employee.class’ knowing that it would extend the abstract RootsRecord class for ORM to the ‘cceis_employees’  table shown earlier (somewhat like the model of the ‘cceis_employees’ table).

I then moved on to the ‘employee.info’ file to author some bits..here’s the source for this file:


name = employee
description = "Manages the Employee Account Records of the CCEIS"
package = CCEIS
core = 7.x
files[] = employee.install
files[] = employee.module
files[] = objects/RootsRecord.class
files[] = objects/Employee.class
configure = admin/config/content/employee/settings
php = 5.2

Once this was complete, I moved on to the ‘employee.install’ file and created this initially:

<?php
/**
 * Implements hook_install().
*/
function employee_install() {
}
/**
 * Implements hook_schema().
*/
function employee_schema() {
}

To get RootsRecord to permit the dereferencing of the table fields as attributes via the Employee object as well as to spawn/set up the cceis_employees table when the module is installed on a fresh Drupal 7 distro, I needed to implement hook_schema based on my custom table cceis_employees.

For this I used the Schema module.

With the Schema module installed, I headed to its configuration controls and found the “inspect” tab. A direct link would be '/admin/structure/schema/inspect'.
Once there, schema lists the hook_install compatible source for all ‘unknown tables’ i.e. tables for modules that did not implement the hook_schema in their install file.

The cceis_employees schema structure source was in this list.

 

In this list, I found the source beginning with $schema[‘cceis_employees’] and copied the generated source and pasted it into the employee_schema() function in the employee.install file.

 

It looked something like this when I was done:

/**
 * Implements hook_schema().
 */
function employee_schema() {
$schema['cceis_employees'] = array(
  'description' => 'TODO: please describe this table!',
  'fields' => array(
    'eid' => array(
      'description' => 'TODO: please describe this field!',
      'type' => 'serial',
      'not null' => TRUE,
    ),
    'last_name' => array(
      'description' => 'TODO: please describe this field!',
      'type' => 'varchar',
      'length' => '50',
      'not null' => FALSE,
      //'sqlsrv_type' => 'varchar',
    ),
    'first_name' => array(
      'description' => 'TODO: please describe this field!',
      'type' => 'varchar',
      'length' => '50',
      'not null' => FALSE,
      //'sqlsrv_type' => 'varchar',
    ),
    'other_name' => array(
      'description' => 'TODO: please describe this field!',
      'type' => 'varchar',
      'length' => '50',
      'not null' => FALSE,
      //'sqlsrv_type' => 'varchar',
    ),
       
...[truncated source]...

  ),
  'primary key' => array('eid'),
);
return $schema;
}

Next, I ensured that the “return $schema;” was placed at the end of the hook function otherwise this would not work.

Once this was set up, I headed over to ‘/objects/Employee.class’ and entered the following source to author the ORM wrapper class for cceis_employees:

<?php

class Employee extends RootsRecord {
       
    protected function table() {
        return 'cceis_employees';
    }
}
?>

The above source is essentially all I needed to create an ORM wrapper for my given custom table. With all this in place, I was able to do CRUD in the following way:

Create:


$employee = new Employee;
$employee->last_name = “Doe”;
$employee->first_name = “John”;
$employee->other_name = “Andy”;
$employee->save();//save() knows when to INSERT or UPDATE
$errors = $employee->getErrors();
          
if(!empty($errors)) {
//unsuccessful insert
}

If all goes well with the insertion, at this point, the $employee object represents the created record in the cceis_employees table complete with the auto generated ID which makes it possible to proceed with subsequent operations that may require the auto inserted ID, for instance.

Read:


$employee = new Employee(23);
$last_name = $employee->last_name;
$first_name = $employee->first_name;
$other_name = $employee->other_name;

Reading requires that the $employee object be created with an argument that matches the primary key value of the record to be read inside of cceis_employees.
Once loaded, dereferencing using the desired fields can be done to grab the value of the record as shown.

Update:


$employee = new Employee(23);
$employee->last_name = “Doodle”;
$employee->first_name = “Jonathan”;
$employee->other_name = “Andy”;
$employee->save();//save() knows when to INSERT or UPDATE
$errors = $employee->getErrors();
          
if(!empty($errors)) {
//unsuccessful update
}

Updating requires that an actual record be loaded by supplying the record’s primary key ID value as a constructor argument of the ORM wrapper class Employee. Once done, adjusted values can be assigned to specified fields as shown.

Delete:


$employee = new Employee(23);
$employee->delete();

Deleting is pretty simple as well.

Look out for more posts to come on integrating Views with this custom, custom ORM