Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building Drupal 6/7 sites ready for Drupal 8

Building Drupal 6/7 sites ready for Drupal 8

With Drupal 8 on the horizon, how can one develop on the currently stable Drupal 6 and 7 versions while preserving future compatibility with Drupal 8 and possibly Silex and Symfony ?

This talks explains how to use independent decoupled components brought in by autoloading and using dependency injection to achieve this goal, the exemple being a commenting service with 78% shared code between the Drupal 6, 7, 8 and Silex versions.

Frédéric G. MARAND

March 29, 2014
Tweet

More Decks by Frédéric G. MARAND

Other Decks in Programming

Transcript

  1. 1/40 | Drupal678 | © 2014 OSInet Drupal 678 Building

    D6/D7 sites ready for D8 – Frédéric G. Marand
  2. 4/40 | Drupal678 | © 2014 OSInet Context: 5 Ws

    Toolbox Back-end process Front-end process Return on experience
  3. 5/40 | Drupal678 | © 2014 OSInet Context-5Ws: WHO •

    Should you follow this process ? • Drupal 7 sites under development in 2014 • Actual deliverable projects, not contrib (juste use D8) • Drupal 6/ Drupal7 sites already in production – which are being worked on to evolve services – but cannot afford a Drupal 8 upgrade yet
  4. 6/40 | Drupal678 | © 2014 OSInet Context-5Ws: WHY •

    You do not want to waste money • On throwaway Drupal 6/7 work, unusable on the next Drupal 8-based iteration of the site on 2015 • You cannot afford a twin sites policy • Live legacy D6/D7, new pages on D8, keep in sync • Needs immediate D8 expertise • Need extra work for sync and to handle complexity
  5. 7/40 | Drupal678 | © 2014 OSInet Context-5Ws: WHEN •

    Until Drupal 8 is good enough for IS • Before Drupal 8.0 in most cases • Before Drupal 8.1 – for companies with a « no *.0 release » corporate policy – if you need some of the extra features poised for 8.1 • Not after that: use 8.x, do not linger
  6. 8/40 | Drupal678 | © 2014 OSInet Context-5Ws: WHAT •

    Covers the business logic code • Code decoupling • Inversion of control • MVC split • Does not replace Drupal 8 • No annotations, plugin system • No menu-related stuff: links, actions, contextual, tasks • No DIC, no automatic EFQ to EQ conversions
  7. 9/40 | Drupal678 | © 2014 OSInet Context-5Ws: WHERE •

    Where can we go with this ? • 70–80% reusable OO code: the bigger part of value • 10–15% config-related code replaced by DIC • 10–15% single-use module code replaced by Drupal 8 • Adjusting the work/value cursor • This is mostly a cost-over-time optimization process • Do not overdo it: rebuilding WSCCI, CMI, reproducing D8 specifics. If you want this, just use D8 beta. • Do not underdo it: no value in just using Twig D7
  8. 10/40 | Drupal678 | © 2014 OSInet Context: 5 Ws

    Toolbox Back-end process Front-end process Return on experience
  9. 12/40 | Drupal678 | © 2014 OSInet Toolbox: Code storage

    • Git is your friend • Isolated repositories: help develop decoupled code • Git submodules: help coordinate updates • Composer • composer.json «repositories» clause • Satis, for private code with zero code hosting budget.
  10. 13/40 | Drupal678 | © 2014 OSInet Toolbox: Class loading

    • Forget Drupal «classic» autoloading • Brittle Drupal 6 autoload module, Drupal 7 registry • Jump to PSR-4 directly. • Composer • Simplest choice, usable on Drupal 6 (custom code) • Composer Vendor, Composer Manager (D7 only) • Xautoload • Closest to Drupal 8, but only for Drupal 7
  11. 14/40 | Drupal678 | © 2014 OSInet Toolbox: Unit Testing

    • Unit testing • SimpleTest unit tests are deprecated • Switch to PHPUnit • Get coverage info with Xdebug + php-code-coverage • Mocking • PHP Unit mocks are D8-standard, but messy • Mockery is simpler and available on D7 • Preparing for D8 means PHPUnit, regrettably.
  12. 16/40 | Drupal678 | © 2014 OSInet Toolbox: Acceptance Testing

    • Behat is not a Drupal 8 standard • Steadily discussed during the Drupal 8 creation cycle • Postponed to Drupal 9 • Situation • Available on any version in «blackbox» mode • Widespread on Drupal 7 with extended features • No incompatibility with D8, expected for D9 • Go for it!
  13. 17/40 | Drupal678 | © 2014 OSInet Context: 5 Ws

    Toolbox Back-end process Front-end process Return on experience
  14. 18/40 | Drupal678 | © 2014 OSInet Backend: Inversion of

    Control • Mile-high overview • Drupal ≤ 7: business logic pulls data • Drupal 8: framework pushes data to business logic • Dependency injection patterns • Constructor injection: for required data • Setter injection: for optional data • Forget property injection • Service Locator is not Dependency Injection
  15. 19/40 | Drupal678 | © 2014 OSInet Backend: code placement

    • Modules: stub + dead code only • Many info hooks are gone in D8: no need to convert them to classes just to throw the result afterwards • For the others, a recurring pattern: – implement the hook to get the proper parameters – fetch a service manager instance – invoke a method on the instance with the parameters • Service managers • D8: ContainerAware • D6/D7: configuration-aware singletons • Use factories, not new, to instantiate services
  16. 20/40 | Drupal678 | © 2014 OSInet Backend: business logic

    • Implemented as instance methods • Code does not fetch from the DB, gets from $this • DI puts the data in the instance • Don't be STUPID, grasp SOLID • SOLID: a hallmark of Drupal 8 • STUPID: Singleton, Tight coupling, Untestability, Premature Optimization, Indescriptive Naming, Duplication • Only binding to procedural code (service managers) implement the singleton pattern as a workaround • http://plusvite.net/3fcaa7f1
  17. 21/40 | Drupal678 | © 2014 OSInet Backend: SOLID practice

    • Single Responsibility Principle • For each module... • At least two *Controller classes: normal pages, admin pages. If you go for one per page, be wary of duplication, refactor to an Abstract Base Controller • One class per form: build/validate/submit handlers • One class per block. Block Derivatives are nice, but you can do without using plain inheritance for most needs • Model your «services» – one service manager per service, no sharing
  18. 22/40 | Drupal678 | © 2014 OSInet Backend: SOLID practice

    • Open/Closed principle • Drupal 8 relies on the «polymorphic» interpretation of Open (for extension)/Closed (for modification) • Define PHP interfaces for all original purpose classes. Name them WhateverInterface • Provide abstract base classes implementing your interfaces. Name them WhateverBase • In methods, always type-hint on the interface (abstraction/contract) not a class (concretion) • (not, OC is not not obsessive/compulsive, why do you ask ? :-) )
  19. 23/40 | Drupal678 | © 2014 OSInet Backend: SOLID practice

    • Liskov substitution • Nothing Drupal-specific. Hint: rectangles are not squares. • Interface Segregation • A sub-case of SRP • If your interface is about two different features, at some point it will need to be split. Do it early. • Demeter • Do not call $foo->bar->baz(). Provide $foo->baz().
  20. 25/40 | Drupal678 | © 2014 OSInet Backend: logging •

    PSR/3 logging standard • PSR/3 standard is implemented in D8, use it in D6/D7: – A PSR/3 watchdog implementation is next to nothing – Use PSR/3 methods, not watchdog(), to avoid porting – Inject the PSR/3 service to your business logic • PSR/3 on D6/D7 – enables better logging while still on these platforms – target Monolog instead of routing messages to watchdog() – you gain lots of targets, filtering, processing... • Monolog might still make it before Drupal 8.0 – work going on (and off) since 2011
  21. 26/40 | Drupal678 | © 2014 OSInet Backend: Core service

    • «Fake it till you make it» not just TDD • Most working logic only needs a few core methods, like t(), variable_*, cache_*, url()/l() • Copy the subset of D8 \Drupal which makes sense for your app • it contains all these methods • do NOT change the signature of what you implement • do NOT implement everything (container...)
  22. 27/40 | Drupal678 | © 2014 OSInet Backend: Guaranteeing •

    None of this is natural for hardcore old-school Drupal developers • So help them with a proper unit test harness • Build a mock implementation of the Core service • Provide it in your abstract base test class • Any dependency on core will fail during tests.
  23. 28/40 | Drupal678 | © 2014 OSInet Storage: inventory •

    Identify information nature • Content: – Store as entities. Not all entities are nodes or terms. – Use EntityAPI and create Entity classes: you will be closer to the D8 implementation than with basic core entities • Variables can be of two different kinds – Configuration : will be in CMI on Drupal 8. Prepare an inventory and rely on it. Variable module (D7) can help. – State : will be in the K/V store on Drupal 8. – You should not be using variables for state anyway. • Something else: consider a storage adapter
  24. 29/40 | Drupal678 | © 2014 OSInet Storage: adapters •

    What they are • Custom logic encapsulating access to a storage resource: non-Drupal tables, S3 bins, REDIS lists... • Uncouple business logic from the underlying storage • Most Drupal 7 projects don't need any – D7: Replace by an entity class for classic content – D7: Replace by a field storage for field-only information, or more likely by an EntityReference to a custom Entity (simpler storage API) – D7: Replace by a stream wrapper for file-type access • Needed for Drupal 6
  25. 30/40 | Drupal678 | © 2014 OSInet Context: 5 Ws

    Toolbox Back-end process Front-end process Return on experience
  26. 31/40 | Drupal678 | © 2014 OSInet Frontend: JavaScript •

    Not overarching plan like backend • Not as much needed • Any upgrade/rebuild for D8 is likely to get a new UI • Some tips • use up-to-date plugins, Backbone, CKEditor • Drupal 8 has a policy of evolving JS in minor versions • consider reducing your use of jQuery for basic operations • run your code through JSHint with D8 .jshintrc
  27. 32/40 | Drupal678 | © 2014 OSInet Frontend: markup/theme •

    Any upgrade/rebuild for D8 is likely to get a new UI: do you care ? • CSS: SMACSS principles • Ready for Twig • https://drupal.org/sandbox/ReneB/1075966 • Maybe not https://drupal.org/project/twig • Probably better served by doing Twig on something else
  28. 33/40 | Drupal678 | © 2014 OSInet Frontend: theme artefacts

    • hook_theme_* hooks still there • mostly unchanged over Drupal 7 • If performance is an issue • The biggest backend theme cost on complexe sites is template resolution in theme() • Favor vector-type operation over single-item ones
  29. 34/40 | Drupal678 | © 2014 OSInet Context: 5 Ws

    Toolbox Back-end process Front-end process Return on experience
  30. 35/40 | Drupal678 | © 2014 OSInet Case Study: situation

    • Actual customer case • Legacy complex Pressflow 6, • lots of custom code • well-trained internal team • high profile Drupal coach • Already two steps of MongoDB • replace dblog/syslog • replace contrib for a complex business feature
  31. 36/40 | Drupal678 | © 2014 OSInet Case Study: problem

    • Core commenting system • Timeline data show slowdown with site success • Millions of users, millions of nodes, tens of millions of comments • Skewed distributions user/node, node/comment, thread size • Business request to page by full threads, not fixed number of comments • Technically • CCK has no alternate field_storage • Business-wise • Amortizing D6 code in 2013 ? Team motivation ?
  32. 37/40 | Drupal678 | © 2014 OSInet Case Study: implementation

    • Tooling • As described previously • Extra: Doctrine ODM for MongoDB • Results • New commenting system – Usable on D7 / D8, as well as non-Drupal PHP sites – 100% S0 test coverage • Code – Custom : 3 kloc. Module: 23%, Decoupled OO code: 77% – Imported : Doctrine: 30, Symfony: 10, misc: 50
  33. 38/40 | Drupal678 | © 2014 OSInet Case Study: lessons

    ? • Tooling • In retrospect, Doctrine ODM not such a good fit • Next time • On Drupal/Pressflow 6: much the same except ODM • On Drupal 7: depending on the business case – significant business logic: much the same except ODM – only storage issues: • rework the Drupal 7 MongoDB entity/field storage • code-wise, stick to D7 fundamendals and EntityAPI where possible • implement any new code as in this example