"Fluid" syntax for hook_schema() (method chaining).

Created on 11 May 2011, almost 14 years ago
Updated 16 January 2025, about 1 month ago

It would be sweet if we had a nicer syntax for hook_schema.
Imo, if we do it right, then shorter = easier to read and write.

D8:
Whether we move this to an event, is a different question, and independent of this proposal.

EDIT (donquixote):
I originally had two ideas about this, but find one a lot more convincing. As so far I am the only one active on this issue, I take the freedom to remove the other proposal.
I recently did some experiments in D7, and this is the (working) result: http://drupal.org/project/injapi
The current proposal is mostly taken from there.

Method chaining / "fluid" syntax for hook_schema().

The hook_schema() would receive an argument that would be the starting point for the method chaining game.
(I say "$api" for "InjectedAPI", following a pattern I use in my contrib projects. Feel free to suggest otherwise.)

The draft is taken directly from injapi module:
http://drupalcode.org/project/injapi.git/blob/refs/heads/7.x-1.x:/exampl...

/**
 * Implements hook_schema()
 */
function hook_schema($schema) {

  // That's the table.
  $table = $schema->table('comment');

  // Primary key.
  // "field_id" automatically gives us auto-increment, and registers this field as the primary key.
  $table->field_id('cid');

  // Foreign keys
  // field_idForeign can take optional parameters to automatically create relations.
  $table->field_idForeign('pid');
  $table->field_idForeign('nid', 'node', 'nid', 'comment_node');
  $table->field_idForeign('uid', 'users', 'uid', 'comment_author');

  // Data fields
  // Most field types have extra parameters for stuff that is special to this type. E.g. varchar length.
  // required() has a second parameter for the default value.
  $table->field_varchar('subject', 64)->required(TRUE, '');
  $table->field_varchar('hostname', 128)->required(TRUE, '');
  $table->field_seconds('created')->required(TRUE, 0);
  $table->field_seconds('changed')->required(TRUE, 0);
  $table->field_unsigned('status', 'tiny')->required(TRUE, 1);
  $table->field_varchar('thread', 255)->required();
  $table->field_varchar('name', 60);
  $table->field_varchar('mail', 64);
  $table->field_varchar('homepage');
  $table->field_varchar('language', 12)->required(TRUE, '');

  // Field descriptions
  // We keep these separate, to have the upper part more readable.
  // This said, we could instead just put the ->description() on each field directly.
  $table->describeFields(array(

    // Id columns
    'cid' => 'Primary Key: Unique comment ID.',
    'pid' => 'The {comment}.cid to which this comment is a reply. If set to 0, this comment is not a reply to an existing comment.',
    'nid' => 'The {node}.nid to which this comment is a reply.',
    'uid' => 'The {users}.uid who authored the comment. If set to 0, this comment was created by an anonymous user.',

    // Data columns
    'subject' => 'The comment title.',
    'hostname' => "The author's host name.",
    'created' => 'The time that the comment was created, as a Unix timestamp.',
    'changed' => 'The time that the comment was last edited, as a Unix timestamp.',
    'status' => 'The published status of a comment. (0 = Not Published, 1 = Published)',
    'thread' => "The vancode representation of the comment's place in a thread.",
    'name' => "The comment author's name. Uses {users}.name if the user is logged in, otherwise uses the value typed into the comment form.",
    'mail' => "The comment author's e-mail address from the comment form, if user is anonymous, and the 'Anonymous users may/must leave their contact information' setting is turned on.",
    'homepage' => "The comment author's home page address from the comment form, if user is anonymous, and the 'Anonymous users may/must leave their contact information' setting is turned on.",
    'language' => 'The {languages}.language of this comment.',
  ));

  // Indexes
  $table->index('comment_status_pid', array('pid', 'status'));
  $table->index('comment_num_new', array('nid', 'status', 'created', 'cid', 'thread'));
  $table->index('comment_uid', array('uid'));
  $table->index('comment_nid_language', array('nid', 'language'));
  $table->index('comment_created', array('created'));
}

Implementation

The implementation can be copied and adapted directly from injapi module.

The objects ($schema, $table, $field) used in here are just temporary for the purpose of this hook, and will not be used anywhere else in the db engine.
The $schema object might be created new for every invocation, or we use the same all over.
The result of a hook invocation will still be a huge array, after all is done.

Benefits

- Nicer to read (imo).
- Faster to type.
- Less scrolling.
- More visual structure in the code.
- Easier to focus on the intention of things, rather than the technical details.
- Well-known patterns such as "auto-increment id" are shortcutted, so you don't have to guess that from the values.
- Possibility for immediate sanity checks in the various methods, so that the array structure collected in $schema can be always sane.

Cons

- A tiny overhead, but that doesn't matter, because it is only on rebuild.
- No idea if we have anything other planned for hook_schema().
- Not sure what to do with hook_schema_alter().

Thoughts

Memory concerns?
We still have the option to run this thing separately per module, with a new $schema object per invocation. I don't think this is actually necessary, but the option exists.

✨ Feature request
Status

Postponed: needs info

Version

11.0 πŸ”₯

Component

database update system

Created by

πŸ‡©πŸ‡ͺGermany donquixote

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • πŸ‡ΊπŸ‡ΈUnited States smustgrave

    Thank you for sharing your idea for improving Drupal.

    We are working to decide if this proposal meets the Criteria for evaluating proposed changes. There hasn't been any discussion here for 8+ years which suggests that this has either been implemented or there is no community support. Your thoughts on this will allow a decision to be made.

    Since we need more information to move forward with this issue, the status is now Postponed (maintainer needs more info). If we don't receive additional information to help with the issue, it may be closed after three months.

    Thanks!

Production build 0.71.5 2024