Presented by Larry Garfield
Click together your own data structure
Click together your own business logic
Click together your own lists and queries
Click together your own site
Just one teeny little problem...
Drupal is an 12 year old, PHP 4-based extensible Slashdot clone originally written by a Belgian college kid.
Drupal needs to evolve, and quickly, from a first-class web CMS into a first-class REST server that includes a first-class web CMS.
How do you convert a system that big?
One bite at a time
Accept that it won't all be done
class Drupal {
protected static $container;
public static function setContainer(ContainerInterface $container) {
static::$container = $container;
}
public static function service($id) {
return static::$container->get($id);
}
public static function entityManager() {
return static::$container->get('plugin.manager.entity');
}
public static function database() {
return static::$container->get('database');
}
}
One global singleton instead of dozens
Migrate services over time
Wherever we land still works
Remove it... eventually
services:
book.manager:
class: Drupal\book\BookManager
arguments: ['@database']
require_once __DIR__ . '/core/includes/bootstrap.inc';
drupal_handle_request();
function drupal_handle_request($test_only = FALSE) {
// Initialize the environment, load settings.php,
// activate a PSR-0 class autoloader with required namespaces registered.
drupal_bootstrap(DRUPAL_BOOTSTRAP_CONFIGURATION);
$kernel = new DrupalKernel('prod', FALSE, drupal_classloader(), !$test_only);
$kernel->boot();
drupal_bootstrap(DRUPAL_BOOTSTRAP_CODE);
// Create a request object from the HttpFoundation.
$request = Request::createFromGlobals();
$response = $kernel->handle($request)->prepare($request)->send();
$kernel->terminate($request, $response);
}
Collaboration FTW!
system.cron:
pattern: '/cron/{key}'
defaults:
_controller: '\Drupal\system\CronController::run'
requirements:
_access_system_cron: 'TRUE'
system.machine_name_transliterate:
pattern: '/machine_name/transliterate'
defaults:
_controller: '\Drupal\system\MachineNameController::transliterate'
requirements:
_permission: 'access content'
aggregator_admin_overview:
pattern: 'admin/config/services/aggregator'
defaults:
_content: '\Drupal\aggregator\Routing\AggregatorController::adminOverview'
requirements:
_permission: 'administer news feeds'
aggregator_admin_settings:
pattern: 'admin/config/services/aggregator/settings'
defaults:
_form: '\Drupal\aggregator\Form\SettingsForm'
requirements:
_permission: 'administer news feeds'
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;
}
}
Any _content route can be a page, or modal, or ajax replacement, automatically!
That's the power of mime types
(_form routes soon as well)
class ParamConverterManager implements RouteEnhancerInterface {
public function enhance(array $defaults, Request $request) {
$converters = array();
$route = $defaults[RouteObjectInterface::ROUTE_OBJECT];
foreach ($this->converters as $converter) {
$converter->process($defaults, $route, $converters);
}
// ...
return $defaults;
}
}
class RouteSubscriber implements EventSubscriberInterface {
/**
* Adds routes on the fly.
*/
public function dynamicRoutes(RouteBuildEvent $event) {
$collection = $event->getRouteCollection();
foreach ($user_settings as $thing) {
$collection->add('route.' . $thing->name, new Route('/thing/' . $thing->name, ...));
}
}
static function getSubscribedEvents() {
$events[RoutingEvents::DYNAMIC] = 'dynamicRoutes';
return $events;
}
}
It's just ControllerResolver
interface ControllerInterface {
/**
* Instantiates a new instance of this controller.
*
* @param ContainerInterface $container
* The service container this object should use.
*/
public static function create(ContainerInterface $container);
}
class AggregatorController implements ControllerInterface {
protected $entityManager;
protected $database;
public static function create(ContainerInterface $container) {
return new static(
$container->get('plugin.manager.entity'),
$container->get('database')
);
}
public function __construct(EntityManager $entity_manager, Connection $database) {
$this->entityManager = $entity_manager;
$this->database = $database;
}
public function feedAdd() {
$feed = $this->entityManager
->getStorageController('aggregator_feed')
->create(array(
'refresh' => 3600,
'block' => 5,
));
// ...
}
}
Depends on your use case
AjaxResponse extends Response
function system_date_time_lookup($form, &$form_state) {
$format = '';
if (!empty($form_state['values']['date_format_pattern'])) {
$format = t('Displayed as %date_format', array(
'%date_format' => format_date(REQUEST_TIME, 'custom', $form_state['values']['date_format_pattern']))
);
}
$response = new AjaxResponse();
$response->addCommand(new ReplaceCommand('#format', '<small id="format">' . $format . '</small>'));
return $response;
}
interface ResourceInterface extends PluginInspectionInterface {
/**
* @return \Symfony\Component\Routing\RouteCollection
*/
public function routes();
/**
* Provides an array of permissions suitable for hook_permission().
*/
public function permissions();
/**
* Returns the available HTTP request methods on this plugin.
*/
public function availableMethods();
}
User-configurable logic
class ArchiverManager extends PluginManagerBase {
public function __construct(\Traversable $namespaces) {
$this->discovery = new AnnotatedClassDiscovery('Archiver', $namespaces);
$this->discovery = new AlterDecorator($this->discovery, 'archiver_info');
$this->discovery = new CacheDecorator($this->discovery, 'archiver_info');
}
public function createInstance($plugin_id, array $configuration = array()) {
$plugin_definition = $this->discovery->getDefinition($plugin_id);
$plugin_class = DefaultFactory::getPluginClass($plugin_id, $plugin_definition);
return new $plugin_class($configuration['filepath']);
}
public function getInstance(array $options) {
return $this->mapper->getInstance($options);
}
}
POPO
/**
* Defines a archiver implementation for .tar files.
*
* @Plugin(
* id = "Tar",
* title = @Translation("Tar"),
* description = @Translation("Handles .tar files."),
* extensions = {"tar", "tgz", "tar.gz", "tar.bz2"}
* )
*/
class Tar implements ArchiverInterface {
public function __construct($file_path) { }
public function add($file_path) { }
public function remove($file_path) { }
public function extract($path, array $files = array()) { }
}
Why aren't you guys just using Doctrine?
allowed_types:
- book
block:
navigation:
mode: 'all pages'
child_type: book
$settings = $configManager->get('book.settings');
$types = $settings->get('allowed_types');
Almost there, deadline is 17 June
(Help would be much appreciated)
Not the ORM, just the annotations library
No wrapper, just Guzzle
(Combined with Simpletest, sorry)
Maybe, now that it has fewer dependencies
We're in UR pull requests, improvin' ur Framework
drupal_set_message()
That whole Symfony CMF Router thing...
We're almost out of time, and need your help
Sprints Friday-Monday
Hi! I'm very excited about symfony2 framework, though I cannot find a good CMS to use with symfony2. Can any of you recommend any?