Presented by Larry Garfield (@Crell)
Drupal 8 is so different,
how will I ever learn it?
The more things change,
the more they stay the same
Many APIs are new
The concepts are much the same
New concepts are there to make life easier
interface HttpKernelInterface {
const MASTER_REQUEST = 1;
const SUB_REQUEST = 2;
/**
* Handles a Request to convert it to a Response.
*
* @param Request $request A Request instance
* @param integer $type The type of the request
* @param Boolean $catch Whether to catch exceptions or not
*
* @return Response A Response instance
*/
public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true);
}
(The concept formerly known as page callbacks and now represented by a PHP callable...)
use Symfony\Component\HttpFoundation\Response;
class MyControllers {
public function hello() {
return new Response('<html><body>Hello World</body></html>');
}
}
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
class MyControllers {
public function hello() {
return new Response('<html><body>Hello World</body></html>');
}
public function helloJson() {
$data['Hello'] = 'World';
return new JsonResponse($data);
}
}
use Symfony\Component\HttpFoundation\StreamedResponse;
class MyControllers {
public function helloCsv() {
$lots_of_data = get_lots_of_data();
$response = new StreamedResponse();
$response->headers->set('Content-Type', 'text/csv');
$response->setCallback(function() use ($lots_of_data) {
foreach ($lots_of_data as $record) {
print implode(', ', $record) . PHP_EOL;
}
});
return $response;
}
}
class MyControllers {
public function helloString() {
return "The view event will turn this into a response.";
}
}
class MyControllers {
public function helloDrupal() {
return array(
'#theme' => 'a_drupal_render_array',
'#description' => 'Those still exist.',
);
}
}
/hello/world/{from}/{to}
use Symfony\Component\HttpFoundation\Request;
class MyControllers {
public function helloDrupal($to, $from, Request $request) {
return array(
'#theme' => 'love_letter',
'#from' => $from,
'#to' => $to,
);
}
}
module.routing.yml
hello.world:
path: '/hello/world/{from}/{to}'
defaults:
_content: '\Drupal\mymodule\Controller\HelloController::helloWorld'
requirements:
_permission: 'access content'
from: \s+
to: \s+
Gah, enough theory, how do I do stuff?
define a module
/modules/hugs/hugs.info.yml
name: Hugs
description: Examples of hugs
type: module
core: 8.x
make a page
/modules/hugs/src/Controller/HugsController.php
namespace Drupal\hugs\Controller;
use Drupal\Core\Controller\ControllerBase;
class HugsController extends ControllerBase {
public function hug($to, $from) {
$message = $this->t('%from sends hugs to %to', [
'%from' => $from,
'%to' => $to,
]);
return $message;
}
}
/modules/hugs/hugs.routing.yml
hugs.hug:
path: /hug/{from}/{to}
defaults:
_content: 'Drupal\hugs\Controller\HugsController::hug'
_title: 'Hug!'
requirements:
_permission: 'access content'
make content themeable
/modules/hugs/hugs.module
function hugs_theme() {
$theme['hug_page'] = [
'variables' => array('from' => NULL, 'to' => NULL),
'template' => 'hug_page',
];
return $theme;
}
/modules/hugs/template/hug_page.html.twig
<section>
{% trans %}
<strong>{{ from }}</strong> hugs <em>{{ to }}</em>
{% endtrans %}
</section>
/modules/hugs/src/Controller/HugsController
namespace Drupal\hugs\Controller;
use Drupal\Core\Controller\ControllerBase;
class HugsController extends ControllerBase {
public function hug($to, $from) {
return [
'#theme' => 'hug_page',
'#from' => $from,
'#to' => $to,
];
}
}
make a make a config form
/modules/hugs/config/install/hugs.settings.yml
default_count: 3
/modules/hugs/config/schema/hugs.settings.yml
hugs.settings:
type: mapping
label: 'Hug module settings'
mapping:
default_count:
type: integer
label: 'Default hug count'
/modules/hugs/src/Form/ConfigForm.php
namespace Drupal\hugs\Form;
use Drupal\Core\Form\ConfigFormBase;
class ConfigForm extends ConfigFormBase {
public function getFormId() {
return 'hug_config';
}
public function buildForm(array $form, array &$form_state) {
$config = $this->config('hugs.settings');
$form['default_count'] = [
'#type' => 'number',
'#title' => $this->t('Default hug count'),
'#default_value' => $config->get('default_count'),
];
return parent::buildForm($form, $form_state);
}
public function submitForm(array &$form, array &$form_state) {
parent::submitForm($form, $form_state);
$config = $this->config('hug.settings');
$config->set('default_count', $form_state['values']['default_count']);
$config->save();
}
}
/modules/hugs/hugs.routing.yml
hugs.config:
path: /admin/config/system/hugs
defaults:
_form: 'Drupal\hugs\Form\ConfigForm'
_title: 'Hug configuration'
requirements:
_permission: 'configure hugs'
/modules/hugs/hugs.menu_links.yml
hugs.config:
title: 'Hugs configuration'
description: 'Configure the hugs system'
route_name: hugs.config
parent: system.admin_config_system
/modules/hugs/hugs.module
function hugs_permission() {
$perms['configure hugs'] = [
'title' => t('Configure the hugs system'),
];
return $perms;
}
/modules/hugs/src/Controller/HugsController
namespace Drupal\hugs\Controller;
use Drupal\Core\Controller\ControllerBase;
class HugsController extends ControllerBase {
public function hug($to, $from, $count) {
if (!$count) {
$count = $this->config('hugs.settings')->get('default_count');
}
return [
'#theme' => 'hug_page',
'#from' => $from,
'#to' => $to,
'#count' => $count
];
}
}
/modules/hugs/hugs.routing.yml
hugs.hug:
path: /hug/{from}/{to}/{count}
defaults:
_content: 'Drupal\hugs\Controller\HugsController::hug'
_title: 'Hug!'
count: 0
requirements:
_permission: 'access content'
count: \d+
/modules/hugs/hugs.module
function hugs_theme() {
$theme['hug_page'] = [
'variables' => array('from' => NULL, 'to' => NULL, 'count' => NULL),
'template' => 'hug_page',
];
return $theme;
}
/modules/hugs/template/hug_page.html.twig
<section>
{% trans %}
<strong>{{ from }}</strong> hugs <em>{{ to }}</em> {{ count }} time.
{% plural count %}
<strong>{{ from }}</strong> hugs <em>{{ to }}</em> {{ count }} times.
{% endtrans %}
</section>
make a block
-- or --
instanceof
"Plugins abstract/automate common OO practices
Learn once, apply everywhere
—Lee Rowlands (core developer)
/modules/hugs/src/Plugin/Block/HugStatus.php
namespace Drupal\hugs\Plugin\Block;
use Drupal\block\BlockBase;
/**
* Reports on hugability status.
*
* @Block(
* id = "hugs_status",
* admin_label = @Translation("Hug status"),
* category = @Translation("System")
* )
*/
class HugStatus extends BlockBase {
public function build() {
return [
'#markup' => $this->t('This is a hug-enabled site'),
];
}
}
/modules/hugs/src/Plugin/Block/HugStatus.php
class HugStatus extends BlockBase {
public function defaultConfiguration() {
return array('enabled' => 1);
}
public function blockForm($form, &$form_state) {
$form['enabled'] = [
'#type' => 'checkbox',
'#title' => $this->t('Hugging enabled'),
'#default_value' => $this->configuration['enabled'],
];
return $form;
}
public function blockSubmit($form, &$form_state) {
$this->configuration['enabled'] = (bool)$form_state['values']['enabled'];
}
public function build() {
$message = $this->configuration['enabled']
? $this->t('Now accepting hugs')
: $this->t('No hugs :-(');
return [
'#markup' => $message,
];
}
}
Make a service
/modules/hugs/src/HugTracker.php
namespace Drupal\hugs;
use Drupal\Core\State\StateInterface;
class HugTracker {
protected $state;
public function __construct(StateInterface $state) {
$this->state = $state;
}
public function addHug($target_name) {
$this->state->set('hugs.last_recipient', $target_name);
return $this;
}
public function getLastRecipient() {
return $this->state->get('hugs.last_recipient');
}
}
/modules/hugs/hugs.services.yml
services:
hugs.hug_tracker:
class: Drupal\hugs\HugTracker
arguments: ['@state']
/modules/hugs/src/Controller/HugsController.php
namespace Drupal\hugs\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\hugs\HugTracker;
use Symfony\Component\DependencyInjection\ContainerInterface;
class HugsController extends ControllerBase {
protected $hugTracker;
public function __construct(HugTracker $tracker) {
$this->hugTracker = $tracker;
}
public static function create(ContainerInterface $container) {
return new static($container->get('hugs.hug_tracker'));
}
public function hug($to, $from, $count) {
$this->hugTracker->addHug($to);
if (!$count) {
$count = $this->config('hugs.settings')->get('default_count');
}
return [
'#theme' => 'hug_page',
'#from' => $from,
'#to' => $to,
'#count' => $count
];
}
}
/modules/hugs/src/Plugin/Block/HugStatus.php
namespace Drupal\hugs\Plugin\Block;
// use ...
class HugStatus extends BlockBase implements ContainerFactoryPluginInterface {
protected $hugTracker;
public function __construct(array $configuration, $plugin_id, $plugin_definition, HugTracker $hugTracker) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->hugTracker = $hugTracker;
}
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration, $plugin_id, $plugin_definition,
$container->get('hugs.hug_tracker')
);
}
public function build() {
$message = $this->t('No hugs :-(');
if ($this->configuration['enabled']) {
$message = $this->t('@to was the last person hugged', [
'@to' => $this->hugTracker->getLastRecipient()
]);
}
return [
'#markup' => $message,
];
}
// ...
}
Work with content
The API is fully baked now!
/modules/hugs/hugs.routing.yml
hugs.node:
path: /node/{node}/hug
defaults:
_content: 'Drupal\hugs\Controller\HugsController::nodeHug'
requirements:
_permission: 'access content'
/modules/hugs/src/Plugin/Block/HugStatus.php
class HugsController extends ControllerBase {
public function nodeHug(NodeInterface $node) {
if ($node->isPublished()) {
// These are the same!
$body = $node->body->value;
$body = $node->body[0]->value;
// Someone convert this function please.
$formatted = check_markup($body, $node->body->format);
$terms = [];
foreach ($node->field_tags as $tag) {
$terms[] = $tag->entity->label();
}
$message = $this->t('Everyone hug @name because @reasons!', [
'@name' => $node->getOwner()->label(),
'@reasons' => implode(', ', $terms),
]);
return [
'#title' => $node->label() . ' (' . $node->bundle() . ')',
'#markup' => $message . $formatted,
];
}
return $this->t('Not published');
}
}
Actually that should probably all be in a template...
The wiring may vary, the approach is the same
Senior Architect, Palantir.net
Making the Web a Better Place
Keep tabs on our work at @Palantir
Want to hear about what we're doing?