Wednesday 16 September 2015

Drupal 8 - How building modules

Drupal 8 is in alpha now, so its a good time to dip your feet in it and notice the major differences Drupal 8 introduced after adopting some Symfony low-level components such as HttpKernel, Routing, EventDispatcher, DependencyInjection, and ClassLoader, for a higher level changes see Hector Iribarne’s presentation on Drupal 8 changes.
In this blog post I will try to build a D8 module and walk through the basic steps.
In this example series we will develop a module and build on it during the coming blog posts.
Lets start.
In brief, our module is basically is a greeting box, visitors come to the site and leave you their greetings and comments, you as an admin can look at these greetings and delete the ones that you do not like. So the basic required architecture is: - A table in the database to hold the greetings. (Model or schema API) - Menu callbacks (Routes) - Controllers to handle the logic.
A good point to note here is that custom and contributed modules/themes now live inside /themes or /modules in the root folder, but still you can place them inside sites/all/modules or sites/all/modules themes.
Like any module in Drupal 7, modules in Drupal 8 need a .info file but this time in a YML format, so we create our hello.info.yml inside our hello directory that we create.
name: Hello
type: module
description: "Hello Drupal 8"
version: 1.x
package: Custom
core: 8.x
Before enabling the module, lefts define our database model, just as in D7 place our code in hello.install
function hello_schema() {
  // Lets keep it basic here and only define the textarea
  $schema['names'] = array(
    'fields' => array(
      'name' => array(
        'type' => 'varchar',
        'length' => 40,
        'not null' => TRUE,
      )
    ),
    'primary key' => array('name'),
  );

  return $schema;
}
After enabling the module we will see the database table that we defined.
Menu callbacks in D7 used to live in .module file but not anymore (will come to that later) after introducing Symfony’s routing system, so lets create a file and call it hello.routing.yml and tell Drupal that we need a page that holds a form for people to greet us.
name_add:
  path: 'add/name'
  defaults:
    _form: '\Drupal\hello\Controller\AddName'
    _title: 'Add your name'
  requirements:
    _permission: 'access content'
Basically telling our module to define a menu route ‘add/name’ that takes its logic from AddName controller, page title should be “add your name”, and anyone with “access content” permission should be able to access this page.
As we said before, menu callbacks do not live in .module anymore, however, its a good practice to add a hook_menu() in the .module file
function hello_menu() {
  $items['add/name'] = array(
    'title' => 'Contact form categories',
    'description' => 'Create a system contact form and set up categories for the form to use.',
    // point to name_add route in hello.routing.yml
    'route_name' => 'name_add',
  );
  
  return $items;
}
Now we should create the controller AddName that will handle displaying the form in the page callback, so create a directory inside the module hello
mkdir -p lib/Drupal/hello/Controller
cd lib/Drupal/hello/Controller
vim AddName.php
In AddName.php
namespace Drupal\hello\Controller;
use Drupal\Core\Form\FormInterface;

class AddName implements FormInterface {
  function getFormID() {
    return 'add_name_form';
  }
  
  function buildForm(array $form, array &$form_state) {
    // The usual Drupal FAPI
    $form['name'] = array(
      '#type' => 'textfield',
      '#title' => t('Your name')
    );
    $form['actions'] = array(
      '#type' => 'actions'
    );
    $form['actions']['submit'] = array(
      '#type' => 'submit',
      '#value' => 'Add'
    );
    return $form;
  }
  
  function validateForm(array &$form, array &$form_state) {
    // Your validation code goes here
  }
  
  function submitForm(array &$form, array &$form_state) {
    $name = $form_state['values']['name'];
    drupal_set_message(t('Added %name as new participant.', array('%name' => $name)));
    // Remember: We should create a HelloStorage controller with the method add() later
    HelloStorage::add($name);
    // Redirect after form submission to add/name that or any other page that exists.
    $form_state['redirect'] = 'add/name';
  }
  
}
Now that we have our page add/name ready, lets create the HelloStorage controller before we try out the submission In the same directory that holds our controllers (lib/Drupal/hello/Controller) create the file HelloStorage.php
namespace Drupal\hello\Controller;

Class HelloStorage {
  
  static function getAll() {
    return db_query('SELECT name FROM {names}')->fetchCol();
  }
  
  static function add($name) {
    db_insert('names')->fields(array('name' => $name))->execute();
  }
  
  static function delete($name) {
    db_delete('names')->condition('name', $name)->execute();
  }

}
}
Alright, the form is ready to work.
Now lets create the admin page that will display the greetings. First we need to define our route in hello.routing.yml
name_list:
  path: 'list/names'
  defaults:
    _content: '\Drupal\hello\Controller\ListNames::content'
    _title: 'People'
  requirements:
    _permission: 'administer greetings'
The route needs a controller to handle it, so in the controllers directory we create the file ListNames.php and we define content() method
namespace Drupal\hello\Controller;

class ListNames {
  public function content() {
    $people = HelloStorage::getAll();
    $output = l('+ Add name', 'add/name');
    $output .= '<table><tr><td>Name</td><td>Delete</td></tr>';
    foreach ($people as $person) {
      $output .= '<tr><td>' . $person . '</td><td><a href="delete/' . $person . '">Delete</a></td></tr>';
    }
    $output .= '</table>';
    return $output;
  } 
}
As we can see from the code above, there is an l() function that create a delete link for the records in the database, that means we need to create a route for that, so in our routing file
name_delete:
  path: 'list/delete/{name}'
  defaults:
    _form: 'Drupal\hello\Controller\DeleteName'
    _title: 'Are you sure?'
  requirements:
    _permission: 'administer greetings'
Now we create our DeleteName controller
namespace Drupal\hello\Controller;
use Drupal\Core\Form\FormInterface;

class DeleteName implements FormInterface {
  function getFormID() {
    return 'delete_name_form';
  }
  
  function buildForm(array $form, array &$form_state) {
    // Just to confirm the deletion
    $form['actions']['submit']['yes'] = array(
      '#type' => 'submit',
      '#value' => 'Yes'
    );
    return $form;
  }
  
  function validateForm(array &$form, array &$form_state) {
    
  }

  function submitForm(array &$form, array &$form_state) {
    $name = arg(2);
    HelloStorage::delete($name);
    drupal_set_message(t('%name was deleted', array('%name' => $name)));
    $form_state['redirect'] = 'list/names';
  }

}
In the next post, we will build over our module to display the greetings in a block (Plugins) and define custom permissions.

No comments:

Post a Comment