Skip to content

Instantly share code, notes, and snippets.

@mparker17
Last active December 17, 2015 16:09
Show Gist options
  • Save mparker17/5636509 to your computer and use it in GitHub Desktop.
Save mparker17/5636509 to your computer and use it in GitHub Desktop.
Notes from DrupalCon Portland 2013

2013-05-21 09:00 We're getting OOP wrong, and there's still time to fix it.

@msonnabaum - Performance engineer at Acquia

D8

  • Symfony componets
  • many more classes
  • still not unit testable

OOP is used to manage complexity.

Domain Model

  • Nouns (from our ubiquitous language)
  • Hold any business logic from the domain
  • Ignorant of the UI

Most of our models are entities

Ideas for making things better

  • Add comments for all properties, especially ambiguous ones.
  • Add descriptive accessor methods.
  • If a different noun appears in multiple methods, you may need a new object.
  • Remove hidden business logic, seriously!
  • Client code show not know how to publish a comment, only that it needs publishing.
  • Extract abstract roles (e.g.: publishable entity interface) from common behaviour
  • Find a balance between single responsibility principle and a rich domain model.
  • Create a new class if necessary

Types of domain objects

  • Entities (non-Drupal):
    • Has identity
    • Mutable
    • Has state
    • Noun
    • Drupal ENtities, COnfig Entities
    • Attributes can change, but it's identity remains.
  • Value objects
    • No identity
    • Immutable (treated as)
    • has stata
    • nouns
    • e.g.: drupal Fields
    • Only identified by it's attributes
  • Services
    • Stateless
    • Verb-ish
    • Create entities and value objects before services

Naming

  • Super-important
  • Not hard
    • Naming classes and methods with poorly-defined roles and responsibilities is hard

  • We have a problem with emphasizing the systems over the domain

Controllers

  • Just using them as page callbacks right now
  • Find business logic in controllers, move to domain objects
  • Create simple accessors for domain objects to simplify further
  • Ideally, Dumb down controllers until they're not worth testing

Unit testing and coupling

  • Be careful: inheritance is hardcoding a dependency!
  • Base classes have the responsibility to not screw their subclasses e.g.: Map class
  • Bare minimum - hide each dependency in a method.
  • The Single Responsibility Principle is equally important to apply to methods.
  • Our code is still tightly coupled
    • If you cannot use PHPUnit to test your class, you cannot claim it's loosely coupled.
    • DrupalUnitTestBase, UnitTestBase are not unit tests.
  • Go convert tests to PHPUnit and prove me wrong!

There is still time to improve our domain model

  • Most changes are just refactorings

Recommended reading

  • Smalltalk best practice patterns, Kent Beck
  • Patterns of Enterprise Architecture, Martin Fowler
  • Practical Object-oriented Design in Ruby, Sandi Metz
  • Domain-Driven Design, ??

2013-05-21 10:15 — Sustainable Core Dev

@heyrocker

  • In order for core to grow, funded contribution is necessary.
  • Reluctance to give funding since results won't be out for 2-3 years.
  • Burnout.
  • 2-3 year release cycle:
    • Pros:
      • Provides long-term stability that clients want
      • Some initiatives will take a long time (WSCCI)
    • Cons:
      • Difficult for developers as they'll have to live with their choices for 2-3 years.

@heyrocker's ideas:

  • Drupal 9 - 3.5 years
  • Layering release cycles on top of that.
    • We shouldn't be afraid to make improvements in minor release cycles
    • If we provide backwards compatibility, we could break APIs mid-release.
      • Backwards compatibility has a performance cost.
      • Decoupled-ness provides better flexibility, should be easier.

Post-presentation Conversation

  • Gabor: Last week, people expected Google to release a new Android OS, but instead they released lots of new features to the core programs that improved stuff
    • Smallcore?
    • Market fragmentation - phone providers customize their distribution of Android so much that new features take a long time to get to everyone's hands.
  • Larry Garfield:
    • Good architecture lets you have nice code without breaking APIs.
    • The trend we're on must continue.
    • Being able to predict how much time we have to throw at problems is the thing that's missing when we're working on Core. - Views initiative did this right.
    • Hard to figure out what to do when we do have time to work on Core
  • XJM (Jess): Maybe we need to maintain two branches - one branch for stable public API, new APIs on a different branch.
  • Would love employer to pay full-, half-time to work on Drupal, how do I convince them to do that?
    • It's super-important for people in the Drupal community to work on Core.

2013-05-21 14:00 — Dependency Injection in D8

@katherinebailey

http://katbailey.github.io/2013/05/15/dependency-injection-in-drupal-8/ http://www.slideshare.net/katbailey/di-drupal8

DI as a design pattern

  • WHy?

    • We want code that is:
      • Clutter free
      • Reusable
      • Testable
  • What is it?

    • Basically passing it in to the constructor
  • We want to write code that is ignorant - the less it knows, the more reusable it is.

  • Forms of DI:

    • constructor injection:
      • Accept interfaces instead of classes in constructors
    • Setter injection:
      • Setter method accepts interface, changes reference in object
    • Interface injection:
      • For every setter, there's an interface for each dependency's setter method
      • Very verbose
      • Not common
  • Inversion of control == Dependency Injection

    • Traditionally, your code calls things in a library.
    • In DI/IoC, the framework calls your code with it's dependencies.
    • "Don't call us, we'll call you."
  • Where does injection happen?

    • Manual injection (example code)
    • Use a factory
    • Use a container / injector (used in frameworks)

DI from a framework perspective

  • Dependency injector = Dependency injection container = IoC Container = Service container

    • Responsible for constructing object graphs based on configuration
  • Objects as services

    • An object that provides global functionality
      • Cache backend
      • Logger
      • Mailer
      • URL generator
  • NOT services:

    • product
    • blog post
    • e-mail message
  • DI containers need to be aware of:

    • Classes
    • Service definitions
    • Control flow / application flow
  • Some frameworks use XML

    • How does it work?
      • Service keys map to service definitions
      • Code asks for service

Symfony-style DI components

  • Stand-alone component

  • Service keys are strings

  • Service definitions specify which class to instantiate, what constructor arguments, allows additional methods to call after instantiation

  • Scope = container

  • Can be configured in PHP, XML, YAML

  • Can be "compiled" to plain PHP

    • Too expensive to parse config each request.
    • Parse once and put the result into a PHP class that hardcodes a method for each service.
  • "Synthetic services"

    • Container doesn't know how to instantiate, but the container can be told about it so it can treat it as a service when someone wants one
  • "Compiler classes"

    • Classes that process the container to manipulate existing service definitions
    • Use to:
      • Process tagged services
  • "Tagged services"

    • Way to flag services
  • "Bundles"

    • Symfony's answer to plugins or modules
    • Each bundle includes a class implementing the BundleInterface which allows it to interact with the container
    • Mention this because we kind-of use it in core, so modules can interact with container
  • Small tangent: Symfony's event dispatcher

    • Olays an important role in the app flow
    • Can be used to dispatch any kind of event
    • Alternative to hook system
    • Listeners are notified when event fires
    • Subscribers are classes that provide event listeners for different events.
    • Compiler pass is used to register all subscribers to the dispatcher using service IDs / tags
    • Holds reference to service container
    • Can therefore instantiate subscriber services with dependencies

DI in Drupal 8

  • Default services:

    • Default DB connection ("database")
    • Module handler ("module_handler")
    • HTTP request object ("request")
  • Two ways to use core's services:

    • Procedural code: use helper: Drupal::service("some_service")
    • Write OO code and get wired into the container
      1. Implement EventSubscriberInterface
      2. Write a service definition and add "event_subscriber" tag
  • How do I get my controller wired in?

    • Hotly debated in Symfony community
    • Controllers have dependencies on services
    • Recommended way in D8 is not to make controllers services themselves, but to implement a special interface that has a factory method which accepts the container
      • Book module is an example
  • Dont' inject the container ever (unless you must)

    • You set a hard dependency, so be careful!
  • Where does it all happen?

    • Drupal Kernel: core/lib/Drupal/Core/DrupalKernel.php
    • Services defined: core/core.services.yml
    • Compiler passes in: core/lib/Drupal/Core/CoreBundle.php
    • Compiler pass classes live here: core/lib/Drupal/Core/DepdendencyInjection/Compiler/...
  • What about modules?

    • Services defined in: mymodule/mymodule.services.yml
    • Compiler passes in: mymodule/lib/Drupal/mymodule/MyModuleBundle.php
    • All classes live in: mymodule/lib/Drupal/mymodule

Easy testability with DI and PHPUnit

  • You can create mock objects :)

      $language_manager = $this->getMock('Drupal\Core\Language\LanguageManager`);
      $lanaguage_manager->expects($this->any())
        ->method('getLanguage')
        ->will($this->returnValue(…));
    

2013-05-21 15:15 — Status of the new Entity API and Typed Data

@the_real_fago @berdir

Overview

  • OO Entity field API
    • What used to be a non-configurable property in D7 is now a configurable field in D8
    • Makes them translatable
    • What's in it:
      • Translation API
      • Validation API
      • Access API
      • Metadata & introspection
      • Computed fields
    • Referenced entities can be lazy-loaded.
    • Matt Observation: looks like entity_metadata_wrapper() in EntityAPI module in D7.

Current API

  • Typed data

    • Way to express metadata in Drupal
    • Reusable interfaces for data structures
    • Use cases:
      • Entity field API
      • Block, Condition, … data arguments ("context")
      • Token?
    • Unification of CTools context and Rules data / Entity property info
    • interfaces:
      • TypedDataInterface
        • Every typed Data object
        • Based upon data definition, including type
        • Examples include primitives, entity types, etc.
      • ComplexDataInterface
        • For data structures containing named properties
      • ListInterface
        • For lists of data items
      • TranslatableInterface, AccessibleInterface, …
    • Plugins (all implement TypedDataInterface):
      • Primitives (string, integer, float, URI)
      • Field items (string_item, uuid_item, entity_reference_item)
      • Field
    • Example: "New comment" marker used to be calculated on the fly, now is a Computed Field
  • Validation

    • based on Symfony Validator
    • constraints as classes
    • Lots of useful constraints built in to Symfony Validator
    • Discovery by D8 plugin system
    • Built into Typed Data API
    • Entity API still needs to implement it.
  • Backward compatibility mode

    • Layers on top of actual entity (Decorator)
    • Temporary
    • Lets you access fields the old way
    • Requires some hacks in new Entity API
    • Used by field API and partially-converted entity types (Node, User)

Status

  • Entity NG conversion in progress
  • Fieldable entities:
    • User
    • Contact message
    • Test
    • Node (still uses BC mode)
  • ConfigEntities:
    • Needs work and discussion
  • Translation:
    • Entity translation in core
    • Converting entity base fields to be translatable [WIP]
  • Validation:
    • There, but not used yet in core
    • Postponed on moving entity objects to fully-implemented the TypedDataInterface
    • To do:
      • Convert field validation and widgets to use it
      • Use validation from the EntityFormController
      • Convert entity base field validation to use it
  • Field API:
    • Finish field types as (data) plugins [#1969728]
    • Make formatters & widgets work on all fields [#1950632]
    • Switch from field-based to entity-based storage. [#1497374]
  • Entity storage:
    • Switch from Field-based to Entity-storage
      • Mixed entity / field storage never worked
      • Entity storage controller handles all
    • Fieldable entity types become extensible entity types
      • Add a storage-only non-configurable file to store something
      • re-use field storage for storing module data
  • Implication: it'll be much more important to use entity field queries in D8 so things can be stored with different backends

Problems

  • Generic TypedData interfaces are too generic
    • Confusing generic methods on objects
  • Getting metadata for stuff you can't instantiate
    • What are the fields of a node?
  • Performance: Multiple magic methods
  • Complexity:
    • Simple way to access field values at the cost of high internal complexity.

Proposals

  • Make use of interfaces for checking what's in an object

      $entity->getType() == 'entity:node'
      // should become
      $entity instanceof NodeInterface
      $value instanceof IntegerInterface
    
  • Remove the TypedDataInterface and add interfaces and methods as suiting (Entity, Field, FieldItem)

  • Move definition arrays to methods and interfaces

  • Way to find fields on a node

    $entityManager->getFieldDefinitions($entity_type, $bundle = NULL)
    
  • Way to find fields on contextual data

  • Improve performance by avoiding unnecessary objects by not enforcing field API structures

  • Improve performance by avoiding magic methods

2013-05-21 17:00 — Accessibility plan for Drupal Core

Jessie Beach https://docs.google.com/document/d/1FPDXnemabQT1UyvMSvrMNgKAy8yPO3fJqFrtpMmXFzU/edit#

  • Accessibility is something that should be in core, not contrib!
  • Accessibility guidelines:
    • We say we support:
      • WCAG
      • ATAG
    • But we don't know if we do.
  • Methods
    • Full review of core admin pages
    • Full review of core templates
    • Scoreboard of elements and grade for each one
    • We could arrange the work just like we did for the HTML5 conversions or TWIG conversions. (we want to specify a definition of done)
  • Tools:
    • WAVE toolbar for Firefox
    • Markup validators
    • QUAIL module, perhaps baked into TestSwarm
    • Automated testing?
    • WYSIWYG integration?
    • Publishing workflow integration
  • Do the guidelines really cover our complex use cases?
  • Four principles of accessibility:
    • Perceivable — able to see something's there
    • Operable — able to take action on something
    • Understandable
    • Robust — make sure they are consistant
  • What needs work?
    • Views! (dialog UI)
    • Fields
    • Content creation
    • Blocks UI
    • Standard aural UI for field states?
  • We need aural UI designers, keyboard UI designers, mobile UI designers, etc.
    • We have the opportunity to be pioneers in this space.
    • We are looking for a champion of aural UI
  • Short-term plan:
    • Testing new, existing stuff.
    • Locating diversity in developers… we scratch our own issues.
  • Do we need an initiative?
    • People should get together to talk about these issues and work on them

2013-05-22 09:00 — Have Fun Coding with Fabien

Small frameworks (like Silex or Twitto)

Challenge: make a micro framework in less than 200 lines of code. Hopefully we'll learn a bit about Symfony in the process.

  1. Create standard Symfony project with composer.phar create-project symfony/framework-standard-edition se2.3.0-rc1
  2. New feature in Symfony 2.3: enter config information
  3. Next step is to cleanup: remove a LOT of files, clean config, composer files.
  4. Convert config files
    • register a stream wrapper
    • put default config in AppKernel.php
  5. Play with the container
    • Create a twig loader stream
    • Add configuration to register the twig loader, give it a tag so other bundles can see it's there.
  6. Find a more-elegant way to halt PHP compilation in the config file
    • __HALT_COMPILER()
  7. Gists as composer repositories
    • Gists are actually git repositories
    • Composer can use Github / git repositories as sources
  8. Making requests

2013-05-22 10:45 — The old and new Field API

@swentel @the_real_fago @yched

New field types

  • More power for site builders out-of-the-box.
  • Not always full ports of the corresponding modules.
  • Moved to core:
    • Entity Reference, because entities are everywhere.
    • Date and time
    • Link
      • Missing: internal URLs
      • Missing: attributes
    • E-mail and telephone
      • e-mail validation
      • no anti-spam report

CMI

  • field_config, field_config_instance gone away , you can deploy through CMI
  • Field and instance definitions in YAML files
  • Human-readable
  • No more serialized storage
  • Can be deployed between environments
  • Deleted fields and instances are kept "in state" until purged
  • Don't hack your active field config
    • Hacking your active views config is usually fine, because it's all run-time stuff
    • But in the case of the Fields API, there's data as well as run-time stuff

API impact: CRUD

  • D7 field_create_field(), field_update_field(), etc are replaced with regular entity API

EntityDisplay

  • In D7, you had to deal with big arrays of display settings that were scattered all around. In D8, there's a ConfigEntity called EntityDisplay that manages the display, and settings are stored in CMI YAML files.

EntityFormDisplay

  • Similar to displaying Entities, there's a EntityFormDisplay to let you configure the form
  • Opens the door to "Form modes", similar to "Display modes"
  • Still in progress — if it doesn't make it into core, it'll be in DS

Plugins

  • Pluggable, custom Widgets, Formatters and Field types.

Everything that was a property is now a field!

  • Every entity property on an entity is now a "field"
  • In D7, base fields ($node->nid, $node->title, $node->uuid) were not configurable; now they are, called "extensible entity types".

Haiku

No content without entity
A property on config
Fields are everywhere

Outlook

  • Make formatters and widgets work on all fields
    • configure the formatter of the node title
  • Re-use existing widgets / formatters with base fields
  • Necessary for in-place editing
  • WIP

Entity field API

  • Field values are objects
  • Field ítem clases === Field type plugins

Computed fields

  • Computed fields are evaluated on the fly.

Improved DX

  • $node->body[LANGUAGE_NONE][0]['value'] is now $node->body->value or $node->body[0]->value
  • There are dedicated helper objects for dealing with translated values.

Know your fields

  • There are ways to find metadata about:
    • Fields
    • Field items
    • Field item values
  • The metadata contains:
    • Label
    • Description
    • Data type
    • etc

TypedData

  • Foundation of the new Entity API
  • A way to express metadata in Drupal
  • Plugins include:
    • primitives (stirng, integer, etc.)
    • field types
    • entity types

Validation

  • Decouples entity validation from form validation
  • Builds upon Symfony Validator
  • Validation constraints are classes…
    • Symfony Validator already provides lots of useful constraints
    • Discovery by the D8 plugin system
  • Built into the Typed Data API
  • To be leveraged by the Entity API
  • EntityFormController will map violations to form errors.

Hidden widget

  • There is now a "hidden" widget in core, applicable to any field type
  • NOT a "hidden" input type — renders nothing
  • Preserves values when saving entity forms
  • Use cases:
    • Programmatically save a value
    • Use rules or custom PHP

Placeholders

  • Only on relevant field types, not globally fields
    • Textfield
    • Tags
    • E-mail
    • Number
    • Textarea

Multiple upload

Misc

  • User picture is now a real field
  • Field translation - syncing of fields (e.g.: image fields - images stay the same, alt / title text changes)
  • More granular permissions on field UI

2013-05-22 13:00 — Silex: An implementation detail

@davedevelopment @igorwesome Code: https://github.com/igorw/doucheswag

Silex

  • A micro framework based on Symfony components.
  • Provides a different way to interact with those components.
  • Based on Pimple (a simple DI container)

Gherkin

  • A business-readable DSL used to describe software behaviour
  • Can act as documentation
  • Can be used for automated tests
  • Behat turns the documentation into tests
  • Depending on how you write your gherkin, you can either:
    • Imperatively create slow, brittle tests that use a headless browser to complete tasks.
    • Declaratively create fast, flexible tests that work with the PHP itself.

Testing pyramid

  1. Small number of end-to-end tests; most are business-facing, few developer-facing.
  2. Medium number of interaction tests; some are business-facing, some developer-facing.
  3. Large number of unit tests; few are business-facing; most are developer-facing.

Architecture

  • Good architecture usually means less-complex code.
  • A framework gives you some structure; but it's mostly infrastructure-type things — it doesn't tell you how to structure your business logic.
    • The architecture of your framework frequently conflicts with your business logic.

EBI architecture

  • EBI = Entity / Boundary / Interactor.
  • Aims to separate business logic from implementation details.
  • Boundary
    • Messages like requests and responses go over the wall.
    • These messages are passed to an interactor.
    • The interactor manipulates entities (like a claw) and returns other messages.

The model layer

  • Consists of:
    • Repositories
      • fetches and stores entities
    • Entities
      • have a concept of identity
      • sometimes (always?) core business objects
      • MattNote: think D8 "Entity domain objects" (from Mark Sonnabaum's "OOP" presentation yesterday)
    • Value object
      • MattNote: think D8 "Value domain objects" (from Mark Sonnabaum's "OOP" presentation yesterday)

Hexagonal architecture

  • Similar to EBI; but takes it to the next level.
  • Business logic at centre.
  • Around the edge are other layers like Repository (via SQL), Web (via Silex), UI (via moustache), Cloud, etc.
  • Each other layer are (mostly) insulated from each-other
  • Business logic cannot depend on the other layers; but the other layers can depend on the business logic.

Silex

  • Attached to routes:
    • Param converter - takes parameter from request and convert it into an object used in your controller
    • Error listener - can tell it how to convert things to HTTP
    • Success handler - can tell it how to convert things to HTTP
  • Can attach metadata to routes so controller can do things

Silex and EBI

  • Using things attached to routes, can convert an HTTP request into a business logic request

EBI and Gherkin

  • You can do both:
    • Use Behat to test business logic directly.
    • Use Behat + Mink to test business logic through the Silex web layer.
  • You describe behaviour in a ubiquitous language
  • You can worry about fulfilling your business value first, and then write the web interface

2013-05-22 14:15 — Love and loss: a Symfony security play

HttpKernel

  1. kernel.request
  2. kernel.controller
  3. kernel.view
  4. kernel.response
  5. kernel.terminate

If something goes wrong, kernel.exception is in there somewhere.

  1. kernel.request
  2. kernel.response
  3. kernel.terminate

Motto: get the response and get out.

Security Listeners — The firewall's henchmen

  • Ask all the listeners. If all say yes, then allowed.

Authentication - are you who you say you are

Authorization - are you allowed to ___?

  • Token: language of security
  • Kind of like a driver's license
  • The core sub-component doesn't know about HTTP, deals with Authentication and Authorization - just abstract
  • The HTTP sub-component deals with tokens
  • In HTTP layer, context (e.g.: username, password) put onto token; token passed to core component
  • Authentication listeners map from HTTP to Authentication manager
  • Authentication manager looks at token, authenticates it, and returns it if it passes
    • Basically, the token has an "authenticated" switch; it's the auth manager's job to flip it based on the context in the token.

Authentication providers

  • Do the actual authentication work
  • Authentication listner(s) talk to auth manager that talks to auth providers, user provider might be behind that.

User providers

  • wrapper to repository of users

Authorization listener

  • Once authentication happens, is run.
  • Decides if an action can be done.

Access map

  • Looks at a request and determines token requirements

Access decision manager

  • The gatekeeper - does the token meet the requirements?
  • Listener -> Mapper -> Decision manager -> Voters

Extension points

  • Firewall has many listeners (listeners maps request data onto token)
  • Auth manager has many auth providers (auth providers decide if token is valid — auth providers may rely on user providers)
  • Access decision manager has many voters (voters may vote based on token, user's roles)
    • An ACL can sometimes be simplified to one or more voters

2013-05-22 15:45 — Modernisation of legacy PHP applications using Symfony2

Statistics:

  • 79% of the top 1 million websites are written in PHP
  • 1994: PHP was created
  • 2004: PHP5 released — start of OOP
  • 2007: Zend Framework 1 and Symfony Framework 1 released
  • => 13 years of (mostly) spaghetti-code website
  • What's the problem?
    • It's not the big that eat the small, it's the fast that eat the slow
    • Spaghetti code is slow to change.
    • Rewrites are even slower…
      • Starting a new version is a danger to the business
      • New developments invisible until new version is finished
      • Probability of forgetting features during rewrite is high
      • Everything impacted at once when transition happens
    • Study on 1471 IT projects:
      • 1 in 6 projects ended up costing 3x more!
      • Large-scale computer spending were 20x more likely to spiral out of control than expected
      • E.g.:
        • Levi Strauss IT — $ 192.5 million losses
        • FoxMeyer Drugs bankruptcy after switching to SAP
  • Black swan blindness — our brain sees the world through bell curves (The Black Swan: The Impact of the Highly Improbable, Nassim Nicholas Taleb)

Progressive rewrite

  • Small iterations are important! / Divide and conquer
  • Progressive rewrite is more profitable — unbeatable time to market.
  • But it's challenging
    • You have to work with hard-to-read code
    • All the aspects of an app in production are concerned:
      • source code of source
      • system
      • cache
      • remote web services
      • data!
    • major conceptual challenge in all those aspects is decoupling

Technical challenges

Steps

  1. Prevent regressions
    1. Functionally test what could harm your business
      • By definition, spaghetti code is deeply coupled - touching one part breaks something else
      • Create functional tests on the most critical senarios
      • How many tests? Etsy deploys 25x per day with >7000 tests
      • Mink and ZombieJS
    2. Set up good monitoring
      • Most thorough functional testing is done by putting in production
      • Monitor production well:
        • 500, 404 errors
        • response time with kingdom
        • NewRelic
      • Make everyone aware of the heightened risks
      • Monitor business metrics with StatsD, give the marketing team a Graphite dashboard => get free high-level monitoring
    3. Create a fast and easy deployment pipeline
      • Deploy often and in small updates (10 times per day?)
      • Setup a fast rollback system.
      • Setup a fast deployment system - you want to make it faster to correct small problems than to rollback.
      • CHeck out Fabric simple automated deployment
  2. Upgrade system
    • (PHP version, etc.)
    • Experience has been that there's usually a 20% speed increase on new server
    • Check that legacy code will support new version
      • PHPCS CodeSniffs for 5.3 and 5.4 compatibility
      • Setup a pre-production environment in the upgraded environment and run your functional tests on it
      • Provision your environment with puppet or chef: check Blueprint
  3. Plugging in to routing
  4. Integrate into current layout
    • For end users, progressive rewrite is synonymous with the same template

    • Copy-pasting the layout is not a good solution

    • Our solution: crawl the legacy layout and include it as ESIs:

      <esi:include src="{{ path('legacylayout_top') }}" /> {% block body %}{% endblock %} <esi:include src="{{ path('legacylayout_footer') }}" />

    • Migrating the UX is not a detail!

      • Progressive modernization of the layout
      • UX is critical in the way your users interact with your business
      • After a big design change, you lose 30% of your daily turnover… what do you do?
      • Food for thought: Amazon does not make big UX changes
  5. Sharing the session and authentication
    • Make the legacy session available from Symfony2
      • You want to access what your legacy app has put in the session, but from within Symfony2
      • But Symfony2 expects session information to be stored in a certain place
      • To make your legacy session info available you need to:
        1. Register Bags for each type of data
        2. ??
      • Check out This Open Source Example
  6. Decoupling the code (attacking the spaghetti problem)
    • Create the dream API for each component, and slowly refactor around it (use the APIs rather than the components themselves) — called the Facade pattern
    • With Symfony2, the new PAI of the Facade pattern is a service
    • Create a bundle for every module you identified
    • Create a service for every bundle you now have
  7. Migrating model and data
    • A typical MySQL database is highly coupled
      • Classical solution is syncing two versions of the data
        • Can be done with ETL tools like Kettle or custom code
        • To avoid headaches, only write to one DB! (ideally the new)
      • You cannot avoid scripting this!
        • Even on non-progressive rewrites, it would be suicide to not regularly test data migration
        • You need to script the migrations from the old model to the new and use them every day on your new developments!
        • Good rewrite from scratch should look like a progressive rewrite.
    • Sometimes non-relational databases make good new databases.
      • Progressive rewrite relies on decoupling, just like scaling. Non relational DBs make partial evolutions easier in the long term
      • In a document DB like Mongo you can have the same collection data which coexist in different versions
      • To migrate them just in time, use /** @MongoDB\PreLoad */ from the Doctrine ORM.

The future

  • Theodo Evolution - R&D project started in 2013
  • Aims to solve all these issue to make app rewriting agile again
  • Theodo is getting ready to open-source a ZF1->SF2 demo
  • Want to eventually create a SF1->SF2 demo
  • Work in progress: real-time sync from PHP+MySQL to Symfony2+MongoDB

2013-05-22 17:00 — Making Big Changes

… to Drupal core

@xjm

  • Big changes help Drupal grow, but there are risks
    • Add to technical debt
    • Span 100-comment issues
    • Are difficult to re-roll or rollback
    • Are hard to review
    • Can cause contributor burnout

Four examples

1805996 Views in Core

  • Started out with 8.x branch in Views
  • Any useful changes to general core were issues in the main Drupal queue
  • Once that was done, sandbox project contained Core+Views merged until it was ready.
  • There were some exceptions made to the process to merge (accessibility, coding style, etc.)

1535868: Blocks as plugins

  • New plugin API, block system, converted all existing blocks, converted all custom blocks, UI change.
  • Small group of dedicated superheroes spent a bunch of time fixing the tests — whole sprints to do this.
  • 400k patch, 80 hours to review, 3000-word summary
  • Lots of merge conflict problems; hard to make progress because trying to chase HEAD
  • Call with Dries and Blocks as Plugins people, result was to commit the patch now and do the rest as follow-ups
  • Bent rules dramatically:
    • 50% performance reduction
    • over 50 follow-ups
    • documentation incomplete
    • still presents some risks to D8 release cycle

Interim analysis

  • Views has been around for 7 years; Blocks was brand-new
  • Views went in 3 months before feature freeze; Blocks went in ???
  • Views was fairly isolated from the rest of core; Blocks touched many parts
    • We didn't convert all core listings when Views was merged.

1735118: Field API to CMI

  • Initially small patch
  • Tests working quickly
  • Prompt responses to questions / issues
  • Only took 8 hours to review patch
  • Lots of feedback; difficult to respond to all the feedback ("daunting and somewhat soul-crushing")
    • xjm tried to condense review
    • Gave xjm access to repo so she could make silly changes
    • This helped

1987510: *.tpl.php to Twig

  • Still in progress
  • So we don't have it half in one and half in other, team has decided to convert all in one patch

Lessons learned

  1. Risk for a big change depends on when it gets committed in the release cycle (early is better)
  2. Split things up…
    1. Scope it!
      • Clearly define the scope — what's involved, what you're doing, what you're not doing
      • Only make one kind of change
    2. Immediately split off self-contained dependencies.
    3. File follow-ups proactively.
  3. Update your issue summaries as often as possible.
  4. Docs and tests are NOT an afterthought
    • Tests explain what something is supposed to do!
  5. Provide interdiffs!
  6. Argue with Testbot in a helper issue to reduce the noise in the main issue.
  7. Smaller patches are more successful. — Think about how you can make it smaller!
    1. 50—60k patches are great
    2. Start with a test implementation.
    3. Use a backwards compatibility layer.
  8. Help your reviewers!
    • help them find things
    • help them file follow-up issues.
  9. Help the people whose code you're reviewing!
    • Organize your reviews
  10. Be an issue manager (or find one).
    • Someone needs to be a PR person for your change.
    • Communicate about the changes you're making.
  11. Build a team.
    • One other person can give you meaningful feedback.
    • There's someone to take it over for you if you get burned out.

Summary

  • Make your issues:
    • Readable,
    • Reviewable,
    • Grokkable.
  • Help each-other.
  • Reduce noise.

Questions & Feedback

  • Do stuff early! It's easier!
  • Two patches in the same issue doesn't help much — reviewers may split it up differently.
  • Tests have to pass always; otherwise people don't know who broke what.
  • Being available on IRC (#drupal-contribute) to talk about your patch is really helpful.

2013-05-23 08:45 — Surviving a Prime-time TV Commercial

@dzuelke

  • Was working for Kiveda, a company that sells kitchens
  • Until recently, was happily running an off-the-shelf e-commerce solution
  • Suddenly an ad on TV during prime-time hours
    • ~10,000 unique visitors within a minute
    • All arrive in a very compressed amount of time and start clicking around
    • Soon, an error page, then a WSOD
    • Make sure the catch-phrases from your commercial are on your website — people type them in.

What will happen first?

  • Apache eating up all RAM?
  • Too many MySQL connections
  • I/O trash/wait halting everything?

Good news

  • an ad on TV during prime time hours airs at a pre-determined time
  • That helps, but you need to make it scale

Options for scaling

  1. Add hardware and caching?
    • And deal with "great" code that might fail
    • Recurring license costs…
    • … per server usually!
    • Rinse and repeat for the next shop you launch!
  2. Build it yourself
    • Investment up front

They went with "Build it yourself"

Goals

  • Home page, product detail, category browser, search <100ms
  • Try to degrade, but not fail, under extreme load
  • Prefer failures for some users over slowness for all users
  • Sacrifice freshness of data and features for performance
  • Replicate any change in product DB to front end in <60s
  • (And don't worry about checkout going down)
    • Defer payment processing until after the load reduces?

For that we need

  • A share-nothing architecture (or at least a "share little" one)
  • Failover configurations where needed (load balancer, cache, …)
    • Developers need to be able to test it.
  • An FTS-compatible database that withstands a lot of parallel reads
  • Ideally an approach where we don't even hit an app server

Traditional hosting or the cloud?

  1. Host everything yourself?
  2. Only use SaaS and components like S3, CDNs, and so forth?
  3. Host some parts yourself and others on AWS or similar?
  4. Host everything in the cloud?

Forget random scale-out from hardware to EC2 (data replication, latency, etc.)

In their case, they chose option B, then C, then B again…

  • Had existing hardware
  • Management believed AWS was too expensive

Special mentions

  • Bundle Inheritance
    • Base implementations for Search, Product, Category…
    • Sites (Kiveda, …) may override templates, controller
  • Twig
    • Share templates between front-end code and e-commerce program (OXID)
  • ElasticSearch
    • Can use for more than search
    • Why not store all data (products, categories, etc) in there?
    • Has a share-nothing architecture
    • Distributed-ness and High Availability built-in

Replicating from MySQL to ES

  • Triggers on product/category/... tables write to a table with commands for replication
  • Script (Symfony CLI) runs periodically, reads replication command sand replays them into ElasticSearch
    • supervisors keeps it alive (crons could overtake each-other)
    • Sends PURGE commands to Varnish

No Gearman, your operations must be serialized

Cache everything (i.e.: as much as you can)

  • Varnish!
    • use grace and saint modes
  • What about shopping cards? Options:
    1. ESIs (set cookie when adding to cart, detect cookie in VCL)
      • Trick for the cart:
        1. Set market cookie (with lifetime) when adding to cart
        2. Page contains an ESI to the cart (just price and number of items)
        3. If cookie exists, hit backend
        4. If not, rewrite location to cart-empty.html in vcl_fetch
        5. Same can be done for logged-in users
      • Note that ESIs are processed sequentially! - can make things slower
      • Automate Everything!
        • CI: Jenkins / Travis, etc.
        • Deployment: Capistrano
        • Provisioning: Puppet or Chef
        • Bring new boxes, bring staging envs (LXC, Xen, …) up quickly
          • Make sure stage mimics production (logical nodes, LB, …)
          • Vagrant!
        • Added benefit: development environments / devops
          • Build VeeWee base box that mirrors production config
          • Use Vagrant and Puppet manifests from production for dev!
          • Quickly test e.g.: a new ElasticSearch node going up or down
          • For the future:
    2. XHR (no server-side work, but latency on client side, and you need to deal with CORS even for just HTTP vs. HTTPS)
      • They segregated traffic by protocol: assume HTTPS traffic is shopping, send to existing shopping cart software
    3. Signed cookies

Tips

  • Test everything
  • Profile your code with XHProf
  • Log errors, performance metrics, user flows
    • And keep this data (disk tape, Hadoop HDFS, etc.)
      • Give your developers access to this data (log stash, HDFS, …)
  • They ran test ads on small TV stations to simulate things
  • https://github.com/j2labs/microarmy - https://github.com/newsapps/beeswithmachineguns - tried to overload their site to see what happens
  • Outsource anything tedious
    • Storage and backups (to S3, …)
    • Content Delivery (CloudFront, CloudFlare, …)
    • Transactional e-mail (Mandrill, Postmark, SendGrid, …)
    • Metrics, logging, monitoring (New Relic, Loggly, Silverline, …)

2013-05-23 09:45 — 10 Lessons from a traveling Symfony2 Circus

@weaverryan github.com/weaverryan

Intro — Reasons to be happy as a Symfony developer

  1. PHP: We, the unexpected generation of quality. * Interfaces * DI 2. Cooperation: * Symfony omponents * AWS SDK * Drupal 3. Interoperability 4. PSR * Instead of using Symfony's logger interface, use Psr\Log\LoggerInterface
  2. Symfony is moving really quickly
    • Most active project on Github

Symfony - Why we haven't all retired to a beach yet

1: Symfony framework is too hard

  • How we see symfony:

    1. Front controller
    2. Container built
    3. Kernel handles request, fires events
    4. Routing returns attributes
    5. Resolver finds the controller, gets its args
    6. Controller is executed, returns a response
    7. ?
  • How a newcomer sees it:

    1. app_dev.php (wtf is this?)
    2. Profit
  • Symfony framework is not hard!

    1. Symfony frameworkisms
    2. Namespaces
    3. Configuration
    4. Directory structure
    5. Shortcuts
  • The level of code shown for advertising should be short, the level of code shown for teaching should be easy-to-understand

  • Hardest part:

    • Directory structure
    • Frameworkisms
    • First
    • 4 hours
  • What is Symfony?

    1. A Routing-controller request-response framework for making web pages
    2. A bunch of optional objects to help you build that page
    3. A DI container with optional pre-built services for your convenience
    4. Optional shortcuts and other Symfony-isms that speed up development.
  • Teaching:

    • Avoid "Frameworkisms"
    • Do things the long way, then opt into shortcuts
    • Start small (Silex!)
    • Remind people that routing+controller is 50% of

2: Symfony framework is too fat

  • The smaller we make Symfony, the faster a user will understand it
    • Only really need FrameWorkBundle, TwigBundle, DoctrineBundle and the custom code.
  • Opt into your level of Shortcuts / Smyfonyisms

3: There be WTF on your timeline

  • Initialization time - building and booting the container
    1. boot bundles, ~1-5ms, affects prod
    2. Rebuild container, ~1-300ms, does not affect prod.
    3. OK, build container, ~500ms-seconds, does not affect prod
    4. Instantiate container, <1ms, affects prod
  • In other words, if you see your initialization time increasing, it's probably just the dev environment.
  • kernel.request.loading
    • Amount of time it takes to instantiate your listeners.
    • How lightweight are your event listeners to construct?
    • Never do work in your listener constructor!
    • Solutions:
      1. Inject the container, so instantiating your object is very simple
      2. Lazy services (Symfony 2.3), so you get "Frankenstein" objects until you need to work with it.
    • If a service is heavy to instantiate, there's no good way to see that yet
    • Heavy instantiation is hidden in event loading statements, the firewall and the controller
    • Having 1000 services is okay, but it's not okay to load all of them for every request (because of dependencies)

4: ACLs

  • How do I enforce that a comment is only editable by it's owner and the super-admin?
    • ACLs create new generic table that stores relationships between objects, users, and what a user can do with the object
    • Written to be highly efficient
    • Don't think you should actually use it.
      • The logic for users editing a comment is very simple; ACLs are overkill
      • Use voters instead
        • Voters can deny, grant or abstain from voting.
        • You can pass an arbitrary object to all the voters
        • Therefore, write your own voter!
          • Invent your own attribute to act on
          • Decide which objects will vote
          • Unit test your voters!
          • Write simple business logic in a voter
          • Use existing relationships to enforce authorization
          • Enforce in your controller at a really high level
  • Performance implications
    • When the firewall runs, all the authentication listeners and voters get instantiated
      • Make sure they are very lightweight!

5: Get wild and crazy and simple with form variables

  • Every form field has variables which give you everything you need to render the form
  • Most form_* vars take an array of variables as arguments
  • You can just dump the form variables in your twig!
  • There's a dump function in twig to help you

6: Get to know HttpKernel

  • At the heart of Drupal
  • Read Fabien's "Create your own framework" series!

7: Play telephone with ESI

  • Use ESI
  • Hard to learn because you can't see what's happening
  • Use AppCache to debug and develop and the X-Symfony-Cache header - it'll tell you what's going on.
    • You can copy those _fragment URLs into your browser and see the individual fragments!

8: SOA and call it a day

9: Moar decoupling

10: ???

Homework

  1. Remove layers, get down to basics
  2. Emphasize Service Oriented Architecture over Symfony
  3. Challenge Symfony, share your solutions.
  4. Sit with a Drupal developer at lunch today (and tell them about composer).

2013-05-23 10:45 — Functional testing with Symfony 2

@qafoo

  • Functional testing is just one part of testing
    • Unit tests (fast)
    • Integration / Functional tests
    • Acceptance tests
    • System (end-to-end) tests (small)

Unit testing

  • Unit testing is testing a very small component of the application, not looking at anything else.
  • Should be small and fast to run
  • Can be hard to teach
  • Sometimes it helps to start writing functional tests - then you don't have to learn how to separate dependencies.

Use multiple feedback cycles…

  • Developer makes unit tests pass and refactor.
  • Project owner facilitates acceptance tests and developer makes them pass.

It's important to find a good mix between slow functional tests and fast unit tests.

Symfony functional tests

  • Looks at application as an HTTP black box.
  • Use a browser (Client) for interaction.
  • Assertions on HTTP responses
  • Integrates into PHPUnit with WebTestCase
  • There is a Symfony2 extension for Behat

Implementation

  • Class with test(s)
  • phpunit.xml file to specify which tests to run.

Symfony2\Component\BrowserKit\Client object

  • Only really used for testing
  • Simulates browser sessions, cookies, and history
  • Implemented by Symfony HttpKernel
    • Wraps around your application kernel
    • Simulates HttpKernelInterface::handle
    • See also @igorw's session "The HttpKernelInterface is a lie"
  • Goutte…
    • Testing "over the wire" using Guzzle HTTP client
    • Time-travel back to @mtdowling's session yesterday

Client#request

  • You can manipulate the:
    • HTTP method
    • relative URI and query string
    • POST parameters
    • Upload files
    • Headers and environment variables
    • POST body string

DomCrawler

  • Simple API for HTML/XML traversal
  • Similar to jQuery API :)
  • Two traversal languages:
    • XPath
    • CSS Selector
  • You can also traverse with a PHP API.
  • Client::request returns a Crawler instance

Click links and submit forms

  • The Client Request and DomCrawler allow you to click links and submit forms
  • Lets you use the Client like a real browser
  • Crawler and Client interaction
  • Benefits:
    • Tests in terms of user-interaction.
    • No URL hardcoding in the tests.

Test setup configuration

  • How do you deal with the stateful-ness of your application?
  • Shared fixture versus Isolated fixture
    • Shared fixture:
      • All tests read/write on the same data
      • No isolation of tests
      • Requires many datasets / rows
      • Order of tests is important!
      • Setup / teardown only happens once.
      • Faster
    • Isolated fixture:
      • Every test uses it's own data
      • Isolation of tests
      • Slow test-setup
      • Micromanagement of fixtures
  • Database setup for tests
    • When do I create this?
      • CI requires automation of schema creation
      • Creating a database is very expensive
      • Do we really need to re-create the entire schema for each test?
        • Decouple the database schema from the tests themselves… tests should assume the database is set up correctly.
    • Use SQLite or a "real" database?
      • SQLite can run in memory! (no I/O overhead)
      • But is SQLite compatible with your application?
    • Share the database connection between every test.
  • How do I work with security so I can test?
    • Options:
      1. Use real authentication — in every test, visit the login page, enter the credentials and log in.
      2. Disable authentication — works if your application doesn't depend on the user object or use the isGranted() method. But, it changes the environment too much; and you can't test security things.
      3. Change authentication strategy — using HTTP Basic Authentication. Favoured.

How do I load fixtures?

  • Different solutions:
    • SQL dumps
      • Easy to maintain
      • Simple
    • PHPUnit DBUnit XMLs
      • Easy to use
      • May be difficult to set up with Symofny
    • Doctrine / Propel fixtures
      • Makes sense if you're using them for something else (initial installation)
  • Keep it simple

Golden rule for Database Testing

Don't use tearDown() to reset (database) fixtures!

  • It's helpful to look at the current state of the database when something goes wrong!

Behat and BDD

  • Gherkin language describes acceptance criteria
  • Mink is a gherkin language for browser interaction
  • Symfony2 Extension using BrowserKit
  • Very useful!

Integration tests with container

  • Using thrid-party code requires integration tests
    • Don't trust their API! :)
    • Caution when mocks simulate wrong behaviour.
  • Examples
    • Code relies on INSERT +ORDER BY of database
    • WebService breaks format or has unreliable uptime
    • Symfony Container is case-insensitive on service ids.
  • Solution:
    • Use real container to construct third-party mocks
  • Tip: Test construction of all your services

2013-05-23 13:00 — Modernizing Drupal using Symfony 2

@crell

What are we using?

  • HttpFoundation
  • HttpKernel
  • Routing
  • Event Dispatcher
  • DependencyInjection
  • Symfony CMF routing
  • ClassLoader
  • YAML
  • Serializer
  • Validator

DI

  • Doing it wrong — we have to do things in stages
  • Global instance of the Symfony DI container, but everyone can call it
  • Eww, a service locator?
    • One global singleton instead of dozens
    • Migrate services over time
    • Wherever we land still works
    • Remove it eventually
  • Register services roughly the same as Symfony full-stack

Routing

  • DrupalKernel, HttpKernel is started early on

HttpKernel

  • Main pipeline in Symfony
  • Same as Symfony (all but one function)

CMF Router

  • Needed more flexibility than core Symfony router
  • Drupal and Symfony co-wrote a CMF Router

Access Control

  • Not using Symfony SecurityBundle

Route enhancer

  • Lets you re-use page output in different places (block, dialogue, etc)

Dynamic routes

  • Modules can create new pages on the fly, without modifying code.

Controllers

  • It's just ControllerResolver
  • Functions (once we remove a backwards-compatibility shiv)
  • Plain Old PHP Objects (POPO) method
  • ContainerAware Object method
  • Services (methods)
  • Clojures (but we don't use them)
  • ControllerInterface:
    • Interface you stick on a class that gives you a container-aware ??

AJAX API

  • AjaxResponse extends Response

REST

  • REST module, replaces Services module
  • Based on Serializer Component
  • HAL out of the box
  • Any entity or other resource are supported
  • Same URI as HTML page (2 patches away)
  • Cookie and token security (working on HttpAuth - love to get that in before code freeze)
  • Entities support following:
    • GET /node/1
    • DELETE /node/1
    • POST /node
    • Used to support PATCH /node/1
  • Non-entities support GET so far (logging)

Serializer

  • Can REST off any supported format
  • New format? write new encoder.
  • New object type? write new normalizer.

Anything non-symfonic

  • Plugins
    • Same concept as CTools Plugins, but awesome and PHP5
    • User-configurable logic
    • Consist of:
      1. Discovery
      2. Instantiation
    • Managers - core of plugin system
  • Entities
    • Basic data object system
    • "Don't call it an ORM" (even though it is)
    • Leverages Validator
    • Version 3, basically
    • … and $node->save() works now!
    • Why aren't we just using Doctrine?
      • We have lots of existing architecture
      • Doctrine is a very different model - not a bad model, but we need more flexibility
      • Need far more runtime behaviour
  • Disk-based configuration
    • No more DB config!
    • (Generated) YAML configuration (YAML component)
    • Stage / import / deploy built in
  • Hooks
    • Procedural aspect-oriented programming
    • But unit testing sucks
    • Will live on (alongside EventDisplatcher) through D8
    • Expect bridge to EventDispatcher
    • Don't expect them in D9
  • TWIG
    • Almost there, deadline is June 17th
    • Not as complete as Symfony Fullstack (inheritance)
    • Help would be much appreciated
  • Doctrine Annotation
    • Not the ORM, just the annotations library
  • Guzzle
    • HTTP client library
    • No wrapper, just Guzzle
    • Nice, we can just pull it in when needed
  • PHPUnit
    • Combined with SimpleTest, sorry
  • PSR-3 Logger
    • Yay for using the common logging interface
    • In the process of moving our system to that model
    • In theory, we could be able to support Monolog
  • Zend _FEED
    • Maybe, now that it has fewer dependencies
  • Misc:
    • HTML5
    • Responsive
    • No IE8
      • jQuery 2!
      • Modern CSS
    • WYSIWYG
    • Inline editor!

Drupal Components

  • PhpStorage - gets around wanting to write PHP files without having a big security risk
  • Gettext

Lets work together!

  • Collaboration
    • Due to Symfony being on MIT license, and Drupal being GPL-2, we can only contribute upstream.
  • What we've done:
    • Improved flash messages
    • File Streaming (added in SF2.2 / D8)
    • Context support in Serializer (SF2.2)
    • Context supoort in Validator (SF2.2)
    • Response::prepare() chaining
    • Improve FlattenException
  • Code freeze is 1 July
    • We're almost out of time and need your help!
    • Sprints Friday - Monday (Acquia Portland office)
    • Jumping in:
      • Come to the sprints
      • Checkout the code
      • #drupal-contribute and #drupal-wscci on IRC
      • Tell us what we're doing wrong / dumb. (Seriously!)
    • We release when we run out of critical bugs
  • Code sprints:
    • Saturday and Sunday are at Acquia office
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment