The road to 8.1

Presented by Larry Garfield (@Crell)

@Crell Pays me!
  • Senior Architect,
  • Drupal 8 Web Services Lead
  • Drupal Representative, PHP-FIG
  • Advisor, Drupal Association
  • Loveable pedant
Drupal 8 .1

Longest release cycle EVAR!

(Can we please never do that again?)

DrupalCon Prague

OK, let's try something else

A new release cycle

The plan

  • Day 0: Drupal 8.0.0
  • 0+1 month: Drupal 8.0.1 (bug fixes)
  • 0+2 month: Drupal 8.0.2 (bug fixes)
  • 0+6 month: Drupal 8.1.0 (new features!)
  • 0+7 month: Drupal 8.1.1 (bug fixes)
  • 0+12 month: Drupal 8.2.0 (new features!)
  • 0+13 month: Drupal 8.2.1 (bug fixes)
  • Drupal LTS: 8.x.0 (last new features)
  • Drupal 9: just API-breaking changes

We don't break APIs in minor versions

(For some definition of API)

Druplicon: API?

An API is a function that performs an operation on a series of arrays.



(This is why we can't have nice things.)

Old API definition

Any line that begins with "function"

(But not if the function starts with _
(except when that's included too because there's no other way to do something
(When your API definition looks like LISP, you have a problem)

And arrays. Lots of arrays.

We broke APIs between versions because our
architecture didn't let us do otherwise

Now we can do otherwise

We won't break modules in minor versions

—Old BC pledge

We won't break well-behaved modules in minor versions

—New BC pledge

Well-behaved modules

  • Uses parts of the code we say are "safe"
  • Uses parts of the architecture we say are "safe"

Documentation problem!

Also, "separate safe from unsafe"

Use documentation to drive separation

Documentation kick-start

Symfony BC policy

Moar @ tags!


  • @api: Safe to type hint, implement, and extend
  • @internal: Caveat implementor
  • Else: Safe to hint, implement, not extend


  • @api: Safe to type hint, __construct(), protected, public
  • @internal: Caveat implementor
  • Else: Safe to type hint, __construct(), public, not extend
  • Methods may be tagged separately

(We should probably treat traits the same)

We won't break @api and we'll warn you about breaks to normal classes/interfaces

—New BC pledge

So what is an @api?

There are no hard rules

They're more like guidelines...

Only guidelines

Strategic considerations

  • Security fixes trump everything
  • This will require case-by-case thought
  • Subsystem maintainers should be responsible
  • Private properties on @api classes?
  • @internal: Reserve the right to change, not willy nilly

General rules

  • Database schema is always @internal
  • Tests are always @internal
  • Controllers are always @internal
  • YAML file structure MAY be an API

Tactical guidelines

  • "Tagged services": @api interface, tag
  • "Tagged service aggregator": @internal class
  • Extend classes (*Base): @api with privates
  • Break base classes into multiple traits
  • Plugins: Mostly @api interface; Manager is @internal

Container guidelines

  • Mark services private (no $container->get())
  • Public services must have an interface
  • Public service names are @api

Declarative guidelines

  • Service tag names: API
  • Routing key names: API
  • Code used by routing key (_form, _entity_view): @internal
  • Config keys: API (unless wrapping service)

Other guidelines

  • 3rd party interfaces: !@api => consider empty extension
  • Base class/interface mismatch: Code smell (needs traits?)
  • Form arrays: Mostly @internal Except node form?
  • Render arrays: Key defs are API, not specific usage

Example: Breadcrumbs

  • BreadcrumbBuilderInterface: @api
  • "breadcrumb_builder" DI tag: @api
  • "breadcrumb" service name: @api
  • ChainBreadcrumbBuilderInterface: normal
  • BreadcrumbManager: @internal
  • BreadcrumbBuilderBase: Remove for traits





  • RouteFilterInterface (wrapped)
  • RouteEnhancerInterface (wrapped)
  • HtmlFragmentInterface


  • RouteSubscriberBase
  • ControllerBase
  • HtmlFragment
  • All exceptions



  • UrlGeneratorInterface
  • RouteBuilderInterface
  • RouteProviderInterface
  • ControllerResolverInterface


  • LazyLoadingRouteCollection
  • RouteBuildEvent
  • UrlGenerator



  • MatcherDumper et al
  • UrlMatcher
  • RouteProvider
  • CompiledRoute
  • Route filters
  • Route enhancers
  • RouteBuilder
  • RouteCompiler
  • RoutePreloader

So where's the API?

It's the YAML, stupid!

  path: '/admin/content'
    _title: 'Content'
    _entity_list: 'node'
    _permission: 'access content overview'
  • _title / _title_callback
  • _content / _controller
  • _form
  • _format / method
  • _entity_view / _entity_form / _entity_list

Changing what we didn't expect

Rename classes/interfaces

class BookManager {
  public function getAllBooks() {}
class BookRepository extends BookManager {
  public function doSomethingNew() {}

function core_cool_stuff(BookRepository $repository) {
  $books = $repository->getAllBooks();

function contrib_cool_stuff(BookManager $manager) {
  $books = $manager->getAllBooks();

Future-looking interfaces

 * @api
interface OldInterface {
  public function foo();

interface NewStyleInterface extends OldStyleInterface {
  public function bar();

function cool_stuff(OldStyleInterface $service) {
  if ($service instanceof NewStyleInterface) {
    $bar = $service->bar();
  else {
    $bar = "Some previously assumed value.";
  // ...

When we do that,
file a D9 issue to clean it up again


We are going to screw up and
want to change something we didn't plan for

Learn by failure

We can raise guarantee, never lower.


We can always raise guarantee, never lower. (So be conservative to start.) Expectation: We're going to screw up and want to change something we didn't setup for that Learning experience! We know not to screw it up in D9. Deadline: 8.0.0.

Larry Garfield

Senior Architect,

Making the Web a Better Place

Keep tabs on our work at @Palantir

Want to hear about what we're doing?