Web Services and Context Symfony
Core Initiative
by Larry Garfield
Wednesday 22 August 2012
@Crell
- Senior Architect, Palantir.net
- Drupal Association Advisor
- Web Services & Context Initiative Owner
- Co-Author,
Drupal 7 Module Development,
Packt Publishing
- Architectural Gadfly
- Nerf Gunslinger
Web Services and Context Core Initiative (WSCCI)
The original plan...
The Web Services and Context Core Initiative (WSCCI) aims to
transform Drupal from a first-class CMS to a first-class REST server with a
first-class CMS on top of it. To do that, we must give Drupal a unified,
powerful context system that will support smarter, context- sensitive,
easily cacheable block-centric layouts and non-page responses using a
robust unified plugin mechanism.
"Pages" are a special case of REST
Drupal 7 page flow
Blocks/Requests in Drupal 8
Where did Symfony2 come from?
- Just wanted a request/response library
- Symfony2 vs. Zend: Symfony2
- Oh, wait, they did this already...
What is Symfony2?
Symfony2 Components
- Reusable set of stand-alone libraries
- Solid Object-Oriented toolset
Symfony2 Framework
- Application framework
- Build on top of the Components
So what are we using?
Drupal 8 page flow (we hope)
HttpKernelInterface
namespace Symfony\Component\HttpKernel;
interface HttpKernelInterface {
/**
* Handles a request.
*
* @param Request $request
* A request instance
* @return $response
* A Response instance
*/
function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true);
}
Excited yet?
I hope so, because we need your help...
What have we gotten done...
What have we gotten done...
- Request/Response
- Kernel-based workflow
- Event Dispatcher
- HttpKernel-based request handling
- Dependency Injection Container
We have all this awesome, but we're not really using it yet
If we stop now, we have a horribly over-engineered mess with few benefits
What do we still have to do...
What do we still have to do
- Finish the new Routing system (still needs ACL)
- Upgrade Ajax system to real-REST
- Advanced Content Negotiation
- Entity Property API unification (see @fago and @dixon_)
- Symfony sessions
- HTTP-based caching
- Block URLs (needed for ESI/partial page caching)
- Port non-HTML requests to new router (e.g. autocomplete)
- SCOTCH-support (figure out page/block conversion to controllers)
- Wither menu links?
What do we still want to do
- Entities Serialization (JSON-LD)
- Outgoing HTTP requests (Guzzle?)
- Path alias refactoring
- Implement Generators (route-backed
url()
)
- Improved BinaryStreamedResponse
- Convert more systems to DI
- Convert low-level hooks to events
- Process form POST earlier to avoid partial page render
- Reduce bootstrap to just settings.php and legacy function loading
- Most of these 30 tagged issues
Symfony2's response API
use Symfony\Component\HttpFoundation\Response;
$response = new Response('No page. :-(', 404, array(
'Content-Type' => 'text/plain',
));
$response = new Response();
$response->setContent('Hello World');
$response->send();
use Symfony\Component\HttpFoundation\StreamedResponse;
$response = new StreamedResponse(function () {
echo 'foo';
flush();
echo 'bar';
});
// Later
$response->send();
EventDispatcher
Like hooks, but
- Object-oriented
- Testable
- More self-evident
- Groupable
EventDispatcher
use Symfony\Component\EventDispatcher\EventDispatcher;
$dispatcher = new EventDispatcher();
// Somewhere...
$callable = function (Event $event) {
// do something
};
$dispatcher->addListener('event_name', $callable);
// Equivalent of module_invoke_all().
$dispatcher->dispatch('event_name', new Event());
class PlusOneSubscribe implements EventSubscriberInterface {
public function onMyEvent(Event $event) {
// Do stuff
}
static function getSubscribedEvents() {
$events['event_name'][] = array('onMyEvent');
return $events;
}
}
$dispatcher->addSubscriber(new PlusOneSubscribe());
HttpKernel
- Ties everything together
- Map a Request to a Response
index.php (before the Container)
use Drupal\Core\DrupalKernel;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
// Create a request object from the HTTPFoundation.
$request = Request::createFromGlobals();
$dispatcher = new EventDispatcher();
$resolver = new ControllerResolver();
$kernel = new DrupalKernel($dispatcher, $resolver);
$response = $kernel->handle($request);
$response->prepare($request);
$response->send();
$kernel->terminate($request, $response);
Controllers
- "Thing the kernel asks for a response."
- Page callback, only better!
- Block, only better!
- Controller == Page callback == Block =>
- Every block is (can be) a subreqest
- Every subrequest is HTTP cacheable
Routing
New routing syntax (farewell hook_menu()
?)
Warning, early untested prototype
function example_route_info() {
$routes['node_page'] = array(
'route' => new Route('/node/{node}', array(
'_controller' => 'NodeController:show',
)),
// This gets objectified later...
'access' => array(
'callback' => array('function' => 'node_access'),
'arguments' => array('view'),
),
);
return $routes;
}
class NodeController {
public function show(Node $node) {
// ...
return new Response($content);
}
}
Routing (fancy)
function example_route_info() {
$routes['blog_list'] = array(
'route' => new Route('/blog/{page}', array(
'_controller' => 'BlogController:show',
'page' => 1, // Default value
), array(
'page' => '\d+',
'_method' => 'GET',
)),
// This gets objectified later...
'access' => array(
'callback' => array('function' => 'node_access'),
'arguments' => array('view'),
),
);
return $routes;
}
class BlogController {
public function show(Request $request, $page) {
// Link to router items, not random URLs.
$link = $this->generator->generate('node_page', array('node' => 5));
// ...
return new Response($content);
}
}
Side-benefits
- Cleaner, more modular code
- More object-oriented (more testable, if we do it right)
- Dependency Injection Container(!)
- Lots of knock-on refactoring to clean up old crap
(Please help clean up old crap!)
A large part of WSCCI is simply paying down years of
technical debt.
That means we can do other cool things.
Things like... SCOTCH
- Blocks and Layout Initiative
- "Panels in Core", without the bad parts
- Talk to Kris Vanderwater (EclipseGc) and Bojhan
Things like... Deployment
- Entity API cleanup
- JSON-LD as standard export format
- => Reading/writing nodes via standard web services becomes easy*!
- Vision - Roadmap
- Talk to fago or Dixon
[*] The management will not be held responsible if "easy" is insufficiently easy for your client.
Collaboration
- Symfony2: MIT License
- Drupal: GPL License
- Code only flows one way. :-(
Work upstream
- Symfony2 "flash messages": Simple session message persistence
drupal_set_message()
- Make less code in the world!
- Drupalers designed needs for improved API
- Drak (of Zikula CMS) implemented it
- All Symfony2-based projects benefit!
- Drupal issue: Reviews desperately needed!
Other issues
- Write secure PHP code to disk: Issue
- Replace drupal_http_request() with Guzzle: Issue
- Clean up Drupal.ajax system: Issue
- Replace drupal_goto() with a Response object: Issue
- Rewrite the installer to not suck: Issue
- And many more... Just look for the WSCCI tag.