Assuming basic understanding of:
cd modules/
git clone git@github.com:palantirnet/drupalgotchi.git
git checkout lab-01-initial
Tamagotchi: http://en.wikipedia.org/wiki/Tamagotchi
Drupalgotchi
cd sites/all/modules/
git clone git@github.com:palantirnet/drupalgotchi.git
git checkout lab-01-initial
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);
}
}
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.',
);
}
}
use Symfony\Component\HttpFoundation\StreamedResponse;
class MyControllers {
public function helloCsvStream() {
$some_data = $this->getFromSomewhere();
$response = new StreamedResponse();
$response->headers->set('Content-Type', 'text/csv');
$response->setCallback(function () use ($some_data) {
foreach ($some_data as $record) {
print implode(', ', $record);
}
});
}
}
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class MyControllers {
public function helloFile() {
$response = new BinaryFileResponse('hello_world.png');
// Do this in settings.php if you know you're on nginx or
// have the Apache module enabled.
$response::trustXSendfileTypeHeader();
return $response;
}
}
use Symfony\Component\HttpFoundation\Response;
class MyControllers {
public function helloEmpty() {
$this->doSomething();
$response = new Response();
$response->setStatusCode(204);
return $response;
}
}
use Symfony\Component\HttpFoundation\Response;
class MyControllers {
public function helloCoffee() {
if ($this->isTeapot()) {
$response = new Response("Don't put coffee in me", 418);
return $response;
}
// ...
}
}
/hello/world/{from}/{to}
class MyControllers {
public function helloDrupal($to, $from, Request $request) {
return array(
'#theme' => 'love_letter',
'#from' => $from,
'#to' => $to,
);
}
}
mymodule.hello_world:
path: '/hello/world/{from}/{to}'
defaults:
_content: '\Drupal\mymodule\Controller\HelloController::helloWorld'
requirements:
_permission: 'access content'
from: \s+
to: \s+
https://github.com/palantirnet/drupalgotchi
git checkout lab-01-initial
Write the following controllers:
{% trans %}
Hello {{ person }}! My name is {{ name }}
{% endtrans %}
Text in {% trans %} is safely escaped. Outside of that, use:
{{ foo|escape('html') }}
Accepts a name as a path parameter, and uses a template to say "Hello $name".
Complete controller code:
git checkout lab-02-controller-complete
(You don't need to know every detail of this.)
KernelEvents::REQUEST
class ContentControllerEnhancer implements RouteEnhancerInterface {
protected $types = array(
'drupal_dialog' => 'controller.dialog:dialog',
'drupal_modal' => 'controller.dialog:modal',
'html' => 'controller.page:content',
);
public function enhance(array $defaults, Request $request) {
if (empty($defaults['_controller']) && !empty($defaults['_content'])) {
$type = $this->negotiation->getContentType($request);
if (isset($this->types[$type])) {
$defaults['_controller'] = $this->types[$type];
}
}
return $defaults;
}
}
Authentication vs. Authorization
(Yep, more listeners)
global $user is dead
Long live the current_user
service!
comment.edit_page:
path: '/comment/{comment}/edit'
defaults:
_entity_form: 'comment.default'
options:
_access_mode: 'ANY'
requirements:
_entity_access: 'comment.update'
_permission: 'edit any comment'
class PermissionAccessCheck implements StaticAccessCheckInterface {
protected $currentUser;
public function __construct(AccountInterface $user) {
$this->currentUser = $user;
}
public function appliesTo() {
return array('_permission');
}
public function access(Route $route, Request $request) {
$permission = $route->getRequirement('_permission');
return $this->currentUser->hasPermission($permission) ? static::ALLOW : static::DENY;
}
}
An object on which your object depends
A generally-stateless "global" object
class Status {
public function show() {
$status = db_query('SELECT status from {table}')->fetchOne();
return $status;
}
}
$status = new Status();
print $status->show();
class Status {
protected $db;
pubic function __construct(Database $db) {
$this->db = $db;
}
public function show() {
$status = $this->db->query('SELECT status from {table}')->fetchOne();
return $status;
}
}
$db = new Database();
$status = new Status($db);
print $status->show();
class Status {
protected $db;
pubic function __construct(DatabaseInterface $db) {
$this->db = $db;
}
public function show() {
$status = $this->db->query('SELECT status from {table}')->fetchOne();
return $status;
}
}
$db = new Database();
$status = new Status($db);
print $status->show();
class Status {
protected $db;
pubic function __construct(Database $db) {
$this->db = $db;
}
public function show() {
$status = $this->db->query('SELECT status from {table}')->fetchOne();
return $status;
}
}
$db_settings = array(...);
$db = new Database($db_settings);
$status = new Status($db);
print $status->show();
"Place where you wire that stuff up in advance"
"Scariest possible name for an array of objects"
$status = $container->get('status');
print $status->show();
services:
router.dynamic:
class: Symfony\Cmf\Component\Routing\DynamicRouter
arguments: ['@router.request_context', '@router.matcher', '@url_generator']
legacy_url_matcher:
class: Drupal\Core\LegacyUrlMatcher
legacy_router:
class: Symfony\Cmf\Component\Routing\DynamicRouter
arguments: ['@router.request_context', '@legacy_url_matcher', '@legacy_generator']
router:
class: Symfony\Cmf\Component\Routing\ChainRouter
calls:
- [setContext, ['@router.request_context']]
- [add, ['@router.dynamic']]
- [add, ['@legacy_router']]
public function createRouter() {
$router = new \Symfony\Cmf\Component\Routing\ChainRouter(
$this->createDynamicRouter(),
$this->createLegacyRouter()
);
$router->setContext($this->createRouterRequestContext());
$router->add($router_dynamic);
$router->add($legacy_router);
return $router;
}
base_field: nid
base_table: node
core: 8.x
description: 'Find and manage content.'
status: '1'
display:
default:
display_options:
access:
type: perm
options:
perm: 'access content overview'
cache:
type: none
query:
type: views_query
exposed_form:
type: basic
Create Admin Settings for Drupalgotchi
Allow administrator to set values:
Start with branch:
git checkout lab-02-controller-complete
drupalgotchi/drupalgotchi.routing.yml
drupalgotchi_settings:
path: '/admin/config/system/drupalgotchi'
defaults:
_form: '\Drupal\drupalgotchi\Form\SettingsForm'
requirements:
_permission: 'administer drupalgotchi'
drupalgotchi/drupalgotchi.module
function drupalgotchi_menu() {
$items['admin/config/system/drupalgotchi'] = array(
'title' => 'Configure Drupalgotchi',
'description' => 'Setup the Drupalgotchi for your site.',
'route_name' => 'drupalgotchi.settings',
);
return $items;
}
drupalgotchi/config/schema/drupalgotchi.settings.yml
# Schema for the configuration files of the Drupalgotchi module.
drupalgotchi.settings:
type: mapping
label: 'Drupalgotchi settings'
mapping:
name:
type: string
label: "The name of your site's persona"
needy:
type: integer
label: 'How needy your site is'
same file name, different location
drupalgotchi/config/drupalgotchi.settings.yml
needy: 10
name: ''
Note how the __construct() uses ConfigFactory
drupalgotchi/lib/Drupal/drupalgotchi/Form/SettingsForm.php
class SettingsForm extends ConfigFormBase {
protected $config;
public function __construct(Config $config) {
$this->config = $config;
}
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory')->get('drupalgotchi.settings')
);
}
public function getFormID() {
return 'drupalgotchi_settings';
}
drupalgotchi/lib/Drupal/drupalgotchi/Form/SettingsForm.php
public function buildForm(array $form, array &$form_state) {
$form['name'] = array(
'#title' => t('Name'),
'#description' => t('What is your site animal\'s name?'),
'#type' => 'textfield',
'#default_value' => $this->config->get('name'),
);
$form['needy'] = array(
'#title' => t('Neediness'),
'#description' => t('How needy the site is for attention. Range is from 1-10.'),
'#type' => 'range',
'#step' => 1,
'#min' => 1,
'#max' => 10,
'#default_value' => $this->config->get('needy'),
);
return parent::buildForm($form, $form_state);
}
drupalgotchi/lib/Drupal/drupalgotchi/Form/SettingsForm.php
public function submitForm(array &$form, array &$form_state) {
parent::submitForm($form, $form_state);
$this->config->set('name', $form_state['values']['name']);
$this->config->set('needy', $form_state['values']['needy']);
$this->config->save();
}
} // end of class
Complete code in branch:
git checkout lab-03-forms-complete
A narrow set of functionality
allows customization and extension of the original system
In Drupal 7?
a new universal plugin system supports:
Managers define and manage plugin types
Example of a block plugin
/* Annotation */
class HelloBlock extends BlockBase {
public function settings() { }
public function blockForm($form, &$form_state) { }
public function blockSubmit($form, &$form_state) { }
public function build() { }
}
find plugins, metadata
parse plugin metadata
use annotations
modulename/
lib/
Drupal/
modulename/
Plugin/
plugintype/
pluginname.php
---
action/
lib/
Drupal/
action/
Plugin/
Action/
EmailAction.php
Annotations provide the metadata
Annotation Types e.g. EntityType, ActionType
use Drupal\Component\Annotation\Plugin; /* required */
/**
* @Plugin(
* ...
* )
*/
Appears just above your plugin class
/**
* Provides a hello block.
*
* @Block(
* id = "drupalgotchi_hello",
* admin_label = @Translation("Hello World"),
* )
*/
core/lib/Drupal/core/Entity/Annotations/EntityType.php
/**
* Provides a hello block.
*
* @Block(
* id = "drupalgotchi_hello",
* admin_label = @Translation("Hello World"),
* )
*/
git checkout lab-03-forms-complete
/**
* Provides a hello block.
*
* @Block(
* id = "drupalgotchi_hello",
* admin_label = @Translation("Hello World"),
* )
*/
public function settings() {
return array(
'person' => 'World',
);
}
public function build() {
return array(
'#theme' => 'drupalgotchi_hello_block',
'#person' => $this->configuration['person'],
);
}
drupalgotchi/templates/drupalgotchi-hello-block.html.twig
---
{% trans %}
Hello there. My name is {{ name }}!
{% endtrans %}
function drupalgotchi_theme() {
return array(
'drupalgotchi_hello_block' => array(
'variables' => array('person' => NULL),
'template' => 'drupalgotchi-hello-block',
)
);
}
Complete code:
git checkout lab-04-blocks-complete
All code in master branch:
git checkout master
Upcoming training events in Chicago, IL
See you online!
Slides: Larry Garfield, Robin Barre
Graphics: Larry Garfield, Ashley Cyborski
©2013 Palantir.net
Interested in website development or training?
Drop us a line!