Node-type specific callbacks (*_load(), *_insert(), *_update(), *_form(), etc.) are not really hooks, but a rather crude implementation of a strategy pattern. Those doesn't fall on the scope of #363787: Plugins: Swappable subsystems for core, but we can still use a proper strategy pattern for that.

/**
 * Implementation of a custom node type.
 */
interface DrupalNodeType {

  /**
   * Load node-type-specific information.
   *
   * This method allow the module to load extra information that it stores about
   * a node. It should not be used to replace information from the core {node}
   * table since this may interfere with the way nodes are fetched from cache.
   *
   * @param $nodes
   *   An array of the nodes being loaded, keyed by nid. At call time,
   *   node.module has already loaded the basic information about the nodes, such
   *   as node ID (nid), title, and body.
   *
   * For a detailed usage example, see node_example.module.
   */
  public function load($nodes);

  /**
   * Respond to node insertion.
   *
   * This method allows the module to take action when a new node is being
   * inserted in the database by, for example, inserting information into
   * related tables.
   *
   * @param $node
   *   The node being inserted.
   * @return
   *   None.
   *
   * To take action when nodes of any type are inserted (not just nodes of
   * the type(s) defined by this module), use hook_node() instead.
   *
   * For a detailed usage example, see node_example.module.
   */
  public function insert($node);

  /**
   * Respond to node updating.
   *
   * This method allows the module to take action when an edited node is being
   * updated in the database by, for example, updating information in related
   * tables.
   *
   * @param $node
   *   The node being updated.
   * @return
   *   None.
   *
   * To take action when nodes of any type are updated (not just nodes of
   * the type(s) defined by this module), use hook_node() instead.
   *
   * For a detailed usage example, see node_example.module.
   */
  public function update($node);

  /**
   * Respond to node deletion.
   *
   * @param &$node
   *   The node being deleted.
   * @return
   *   None.
   *
   * To take action when nodes of any type are deleted (not just nodes of
   * the type defined by this module), use hook_node() instead.
   *
   * For a detailed usage example, see node_example.module.
   */
  public function delete(&$node);

  /**
   * Display a node.
   *
   * This method allows a module to define a custom method of displaying its
   * nodes, usually by displaying extra information particular to that node type.
   *
   * @param $node
   *   The node to be displayed.
   * @param $teaser
   *   Whether we are to generate a "teaser" or summary of the node, rather than
   *   display the whole thing.
   * @return
   *   $node. The passed $node parameter should be modified as necessary and
   *   returned so it can be properly presented. Nodes are prepared for display
   *   by assembling a structured array in $node->content, rather than directly
   *   manipulating $node->body and $node->teaser. The format of this array is
   *   the same used by the Forms API. As with FormAPI arrays, the #weight
   *   property can be used to control the relative positions of added elements.
   *   If for some reason you need to change the body or teaser returned by
   *   node_prepare(), you can modify $node->content['body']['#value']. Note
   *   that this will be the un-rendered content. To modify the rendered output,
   *   see hook_node($op = 'alter').
   *
   * For a detailed usage example, see node_example.module.
   */
  public function view($node, $teaser = FALSE);

  /**
   * Prepare a node to be rendered on the node form.
   *
   * @param &$node
   *   The node being prepared.
   * @return
   *   None.
   */
  public function prepare(&$node);

  /**
   * Display a node editing form.
   *
   * This method is called to retrieve the form that is displayed when one
   * attempts to "create/edit" an item. This form is displayed at the URI
   * http://www.example.com/?q=node/<add|edit>/nodetype.
   *
   * @param &$node
   *   The node being added or edited.
   * @param $form_state
   *   The form state array. Changes made to this variable will have no effect.
   * @return
   *   An array containing the form elements to be displayed in the node
   *   edit form.
   *
   * The submit and preview buttons, taxonomy controls, and administrative
   * accoutrements are displayed automatically by node.module. This hook
   * needs to return the node title, the body text area, and fields
   * specific to the node type.
   *
   * For a detailed usage example, see node_example.module.
   */
  public function form(&$node, $form_state);

  /**
   * Verify a node editing form.
   *
   * This method allows the module to verify that the node is in a format valid
   * to post to the site. Errors should be set with form_set_error().
   *
   * @param $node
   *   The node to be validated.
   * @param $form
   *   The node edit form array.
   * @return
   *   None.
   *
   * To validate nodes of all types (not just nodes of the type(s) defined by
   * this module), use hook_node() instead.
   *
   * Changes made to the $node object within a hook_validate() function will
   * have no effect. The preferred method to change a node's content is to use
   * hook_submit() or hook_node($op='submit') instead. If it is really
   * necessary to change the node at the validate stage, you can use function
   * form_set_value().
   *
   * For a detailed usage example, see node_example.module.
   */
  public function validate($node, &$form);

  /**
   * Define access restrictions.
   *
   * @param $op
   *   The operation to be performed. Possible values:
   *   - "create"
   *   - "delete"
   *   - "update"
   *   - "view"
   * @param $node
   *   The node on which the operation is to be performed, or, if it does
   *   not yet exist, the type of node to be created.
   * @param $account
   *   A user object representing the user for whom the operation is to be
   *   performed.
   * @return
   *   TRUE if the operation is  to be allowed;
   *   FALSE if the operation is to be denied;
   *   NULL to not override the settings in the node_access table, or access
   *     control modules.
   *
   * The administrative account (user ID #1) always passes any access check,
   * so this hook is not called in that case. If this hook is not defined for
   * a node type, all access checks will fail, so only the administrator will
   * be able to see content of that type. However, users with the "administer
   * nodes" permission may always view and edit content through the
   * administrative interface.
   *
   * For a detailed usage example, see node_example.module.
   */
  public function access($op, $node, $account);

}

Comments

david strauss’s picture

I wouldn't even call it a "strategy pattern." Modules implementing node types are basically subclassing a generic "node" class that currently has a title and body.

Thus, if we want to do this right, the node module should have a class that it uses for its own (title + body) node types, and modules creating customized node types should subclass it. Then, we use the factory pattern for getting the right node class for any node.

mfer’s picture

Subscribe.

Crell’s picture

Interfaces should be named SomethingInterface, per all of the other interfaces in core.

That said, I disagree with David in #1. Node types should not be subclasses. That's far too brittle given how much sideways stuff we do in Drupal. I'd actually argue the other direction: With fields in core, pseudo-hook_load et al should go away entirely and everything should be done through fields. Of course, that requires a very flexible field API so that all that wacky custom code can be reimplemented as custom wacky fields. :-)

david strauss’s picture

@Crell Yes, I've been known to argue that as well. I was simply suggesting something less radical for now.

xano’s picture

Subscribing.

sun’s picture

Version: 7.x-dev » 8.x-dev
Crell’s picture

Status: Active » Fixed

I think this already did happen in perhaps a roundabout fashion, no? It certainly doesn't apply anymore in the new Entity API...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.