StackStarter.io/spin
Spin Code: TRAININGDAY
StackStarter.io/spin
A world of Drupalisms...
A whole new world...
A modernized architecture and consistant APIs, while still being Drupal!
The larger PHP community has been evolving
Symfony Components (HTTP Foundation, Routing, Dependency Injection, YAML, etc)
Doctrine Annotations
Twig theme system
PHP Unit for testing
For a full list:
/core/composer.json
Abstraction
Encapsulation
Inheritance
Polymorphism
/modules/custom/hello/hello.info.yml
name: Hello
description: A friendly Drupal 8 module
type: module
core: 8.x
Modules now live in the /modules directory
We don't need a .module file anymore!
Without hook_menu!
/**
* Our first Drupal 8 controller.
*/
namespace Drupal\hello\Controller;
use Drupal\Core\Controller\ControllerBase;
class HelloController extends ControllerBase {
public function sayHi() {
return array(
'#markup'=>"Why hello Drupal 8",
);
}
}
/modules/custom/hello/hello.routing.yml
hello.sayHi:
path: '/hello'
defaults:
_controller: '\Drupal\hello\Controller\HelloController::sayHi'
requirements:
_permission: 'access content'
/**
* Our first Drupal 8 controller.
*/
namespace Drupal\hello\Controller;
use Drupal\Core\Controller\ControllerBase;
class HelloController extends ControllerBase {
public function sayHi($name) {
$phrase = $this->t("Hello @name", array("@name"=>$name));
return array(
'#markup'=>$phrase,
);
}
}
/modules/custom/hello/hello.routing.yml
hello.sayHi:
path: '/hello/{name}'
defaults:
_controller: '\Drupal\hello\Controller\HelloController::sayHi'
name: 'Nick'
requirements:
_permission: 'access content'
/modules/custom/hello/hello.routing.yml
hello.sayHi:
path: '/hello/{name}'
defaults:
_controller: '\Drupal\hello\Controller\HelloController::sayHi'
name: 'Nick'
requirements:
_permission: 'access content'
name: ^N[a-z]+
Our route is now a bit antisocial
/modules/custom/hello/hello.links.menu.yml
hello.sayHi:
title: "Say Hi"
route_name: hello.sayHi
menu_name: tools
/modules/custom/hello/hello.permissions.yml
request hello:
title: 'Request Hello'
This will look familar
/modules/custom/hello/hello.module
/**
* Implements hook_theme
*/
function hello_theme() {
$theme['say_hello'] = array(
'variables' => array('name'=>null),
'template' => 'say_hello',
);
return $theme;
}
/modules/custom/hello/templates/say_hello.html.twig
<section>
{% trans %}
Hello {{ name }}
{% endtrans %}
</section>
/**
* Our first Drupal 8 controller.
* Lets say HI
*/
namespace Drupal\hello\Controller;
use Drupal\Core\Controller\ControllerBase;
class HelloController extends ControllerBase {
public function sayHi($name) {
return array(
'#theme'=>'say_hello',
'#name' => $name,
);
}
}
Very similar pattern!
/modules/custom/hello/src/Form/HelloRequestForm.php
/**
* @file
* Contains \Drupal\hello\Form\HelloRequestForm.
*/
namespace Drupal\hello\Form;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
class HelloRequestForm extends FormBase {
/**
* {@inheritdoc}.
*/
public function getFormId() {
return 'hello_request';
}
/**
* {@inheritdoc}.
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$form['phone_number'] = array(
'#type' => 'tel',
'#title' => $this->t('Your phone number')
);
$form['actions']['#type'] = 'actions';
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => $this->t('Give me a call'),
'#button_type' => 'primary',
);
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, FormStateInterface $form_state) {
if (strlen($form_state->getValue('phone_number')) < 3) {
$form_state->setErrorByName('phone_number', $this->t('The phone number is too short. Please enter a full phone number.'));
}
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
drupal_set_message($this->t('Your phone number is @number', array('@number' => $form_state->getValue('phone_number'))));
}
}
/modules/custom/hello/hello.routing.yml
hello.requestHi:
path: '/hello/request'
defaults:
_form: '\Drupal\hello\Form\HelloRequestForm'
requirements:
_permission: 'request hello'
Information about your site that is not content and is meant to be more permanent, such as the name of your site, the content types and views you have defined, etc.
Two types: Simple Configuration and Configuration entities
/modules/custom/hello/config/install/hello.settings.yml
default_count: 10
phrase: "Why hello"
\Drupal::config('hello.settings')->get('default_count');
/modules/custom/hello/src/Form/HelloConfigForm.php
/**
* @file
* Contains \Drupal\hello\Form\HelloConfigForm.
*/
namespace Drupal\hello\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
class HelloConfigForm extends ConfigFormBase {
/**
* {@inheritdoc}.
*/
public function getFormId() {
return 'hello_config';
}
/**
* Declare what settings this config form will be changing.
*/
protected function getEditableConfigNames() {
return ['hello.settings'];
}
/**
* {@inheritdoc}.
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config('hello.settings');
$form['default_count'] = array(
'#type' => 'number',
'#title' => $this->t('How many times do we say hi?'),
'#default_value' => $config->get('default_count'),
);
$form['phrase'] = array(
'#type' => 'textfield',
'#title' => $this->t('How do you want to say hi?'),
'#default_value' => $config->get('phrase'),
);
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
parent::submitForm($form, $form_state);
//lets load and set our configuration.
$config = $this->config('hello.settings');
$config->set('default_count', $form_state->getValue('default_count'));
$config->set('phrase', $form_state->getValue('phrase'));
//save the config.
$config->save();
}
}
/modules/custom/hello/hello.routing.yml
hello.configHi:
path: '/hello/config'
defaults:
_form: '\Drupal\hello\Form\HelloConfigForm'
requirements:
_permission: 'access content'
Without hook_block_info and hook_block_view!
/modules/custom/hello/src/Plugin/Block/HelloBlock.php
namespace Drupal\hello\Plugin\Block;
use Drupal\Core\Block\BlockBase;
/**
* Provides a 'Hello' block.
*
* @Block(
* id = "hello_block",
* admin_label = @Translation("Hello block"),
* )
*/
class HelloBlock extends BlockBase {
public function build() {
return array(
'#markup' => $this->t("This is a an awesome block!"),
);
}
}
/**
* @file
* Contains \Drupal\hello\Plugin\Block\HelloBlock
*/
namespace Drupal\hello\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Form\FormStateInterface;
/**
* Provides a 'Hello' block.
*
* @Block(
* id = "hello_block",
* admin_label = @Translation("Hello block"),
* )
*/
class HelloBlock extends BlockBase {
public function defaultConfiguration() {
return ['count' => 1];
}
/**
* {@inheritdoc}
*/
public function blockForm($form, FormStateInterface $form_state) {
$form['hello_count'] = array(
'#type' => 'number',
'#title' => t('Hello Count'),
'#size' => 10,
'#description' => t('The number of times the block say hi.'),
'#default_value' => $this->configuration['count'],
);
return $form;
}
/**
* {@inheritdoc}
*/
public function blockSubmit($form, FormStateInterface $form_state) {
$this->configuration['hello_count']
= $form_state->getValue('hello_count');
}
/**
* {@inheritdoc}
*/
public function build() {
$count = $this->configuration['hello_count'];
return array(
'#markup' => $this->t("Hello {$count} times!"),
);
}
}
Plugins Everywhere
Small (as possible) stateless objects that perform some testable task.
This opens up an interesting and powerful world for us...
A service container (or dependency injection container) is a PHP object that manages the instantiation of services.
namespace Drupal\hello;
class HelloService {
//dead simple example.
public function doSomeWork() {
//some heavy lifting.
drupal_set_message("Hello Services");
}
}
/modules/custom/hello/hello.services.yml
services:
hello.hello_service:
class: Drupal\hello\HelloService
/**
* Our first Drupal 8 controller with a custom service!
* Lets say HI
*/
namespace Drupal\hello\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\hello\HelloService;
use Symfony\Component\DependencyInjection\ContainerInterface;
class HelloController extends ControllerBase {
protected $helloServ;
//our contructor is injected with our service.
public function __construct(HelloService $s) {
$this->helloServ = $s;
}
//This is the interesting method. ControllerBase will call this.
public static function create(ContainerInterface $container) {
//and create an instance of this class, injecting our service!
return new static($container->get('hello.hello_service'));
}
public function sayHi($name) {
//lets use that bad boy!
$this->helloServ->doSomeWork();
return array(
'#theme'=>'say_hello',
'#name' => $name,
);
}
}
Leveraging the Symfony Framework Events.
List of core events: http://bit.ly/core_events
namespace Drupal\hello\EventSubscriber;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Drupal\Core\Session\AccountInterface;
class HelloEventSubscriber implements EventSubscriberInterface {
protected $account;
public function __contruct(AccountInterface $account) {
$this->account = $account;
}
public function checkForSomething(GetResponseEvent $event) {
drupal_set_message("Big brother is here!");
}
/**
* {@inheritdoc}
*/
static function getSubscribedEvents() {
$events[KernelEvents::REQUEST][] = array('checkForSomething');
return $events;
}
}
/modules/custom/hello/hello.services.yml
hello_event_subscriber:
class: Drupal\hello\EventSubscriber\HelloEventSubscriber
arguments: ['@current_user']
tags: #Our Service Tags
- {name: event_subscriber}
So much more to explore and learn...
Thank you!
Email: nick@segosolutions.com
Twitter: @direct
Slides Available:
http://nickgs.github.io/drupal8-testdrive-module