On Github Crell / slides-d8-for-devs
Palantir.net
Presented by Larry Garfield (@Crell)
—Drupal developers
—Everyone else
The more things change,the more they stay the same
The concepts are still Drupal
The achitecture is modernized
The APIs are more consistent
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 callbacksand now represented by a ()
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; } }
use Symfony\Component\HttpFoundation\BinaryFileResponse; class MyControllers { public function privateFile() { return new BinaryFileResponse('secret_plans.odt'); } }
class MyControllers { public function helloDrupal() { return array( '#theme' => 'a_drupal_render_array', '#description' => 'Those still exist.', ); } }
use Symfony\Component\HttpFoundation\Request; class HelloController { 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: _controller: '\Drupal\mymodule\Controller\HelloController::helloDrupal' 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 ['#markup' => $message]; } }
/modules/hugs/hugs.routing.yml
hugs.hug: path: /hug/{from}/{to} defaults: _controller: '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' => ['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 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; use Drupal\Core\Form\FormStateInterface; class ConfigForm extends ConfigFormBase { public function getFormId() { return 'hug_config'; } protected function getEditableConfigNames() { return ['hugs.settings']; } public function buildForm(array $form, FormStateInterface $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, FormStateInterface $form_state) { parent::submitForm($form, $form_state); $config = $this->config('hug.settings'); $config->set('default_count', $form_state->getValue('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.permissions.yml
configure_hugs: title: 'Configure the hugs system' description: 'Configure default hug count'
/modules/hugs/hugs.links.menu.yml
hugs.config: title: 'Hugs configuration' description: 'Configure the hugs system' route_name: hugs.config parent: system.admin_config_system
/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: _controller: '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' => ['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 --
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 ['enabled' => 1]; } public function blockForm($form, FormStateInterface $form_state) { $form['enabled'] = [ '#type' => 'checkbox', '#title' => $this->t('Hugging enabled'), '#default_value' => $this->configuration['enabled'], ]; return $form; } public function blockSubmit($form, FormStateInterface $form_state) { $this->configuration['enabled'] = (bool)$form_state->getValue('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); $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; 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: _controller: 'Drupal\hugs\Controller\HugsController::nodeHug' requirements: _permission: 'access content'
/modules/hugs/src/Controller/HugsController.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; // But we really want... $formatted = $node->body->processed; 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 ['#markup' => $this->t('Not published')]; } }
That should probably all be in a template...
The wiring may vary, the approach is the same
Senior Architect, Palantir.net
Let's Make Something Good Together.
Keep tabs on our work at @Palantir