Bring your PHP application to the next level with React.JS

Bring your PHP application to the next level with React.JS

React.JS is a very powerful library for building user interfaces and the new, cool kid in the JavaScript world. But it is also very interesting for creating a fast, maintainable and interactive PHP application. In this talk I'll show what React is all about and how you can combine it’s power with your PHP application, even if you have to render out HTML on the server side. The result will be a flexible, component based architecture that enables developers to take page elements, like a single "follow" button, and place it everywhere on the site without having to duplicate any PHP, JS, HTML, CSS or AJAX endpoints in the process.

Ded87c77266697ee6981c2277bb97633?s=128

Bastian Hofmann

February 19, 2015
Tweet

Transcript

  1. Bring your PHP application to the next level with React.JS

    @BastianHofmann
  2. Let's talk about...

  3. Application architecture

  4. Code and component  re-use

  5. Rapid Development

  6. Handling large code-bases

  7. With PHP and React.js

  8. webserver HTML browser JS controller

  9. webserver HTML browser JS Ajax controller

  10. webserver HTML browser JS Ajax controller

  11. de duplication ode duplication code duplication code duplication code duplication

    code duplication code duplication code duplication code duplication code duplication code duplication code duplication code duplication code duplicatio code duplicat code duplic code dup code du code cod co co
  12. So?

  13. A few words about me

  14. None
  15. None
  16. None
  17. Questions? Ask

  18. http://speakerdeck.com/u/bastianhofmann

  19. Application architecture

  20. many roads

  21. None
  22. status quo

  23. webserver loadbalancer pgsql memcached mongodb services

  24. webserver

  25. None
  26. MVC

  27. PHP templates

  28. Duplication and only some Code re- use

  29. We can do  better

  30. Small components over big controllers

  31. None
  32. None
  33. None
  34. None
  35. None
  36. None
  37. None
  38. None
  39. Self contained

  40. Can be used everywhere

  41. Can be addressed and rendered separately

  42. JS is part of the component

  43. CSS can be part of the component

  44. Share code between server and client

  45. It needs to be fast

  46. http://facebook.github.io/react/

  47. JavaScript UI Library

  48. Virtual DOM

  49. Many small, self contained, reusable components

  50. var HelloMessage = React.createClass(
 {
 render: function () {
 return

    <div>Hello {this.props.name}</div>;
 }
 }
 );
 
 React.render(<HelloMessage name="John" />, mountNode);
  51. var Timer = React.createClass({
 getInitialState: function () {
 return {secs:

    0};
 },
 tick: function () {
 this.setState({secs: this.state.secs + 1});
 },
 componentDidMount: function () {
 this.interval = setInterval(this.tick, 1000);
 },
 componentWillUnmount: function () {
 clearInterval(this.interval);
 },
 render: function () {
 return (<div> Seconds Elapsed: {this.state.secs}</div> );
 }
 });
 
 React.render(<Timer />, mountNode);
  52. var TodoList = React.createClass(
 {
 render: function () {
 var

    createItem = function (itemText) {
 return <li>{itemText}</li>;
 };
 return ( <ul> {this.props.items.map(createItem)} </ul> );
 }
 }
 );
  53. var TodoApp = React.createClass({
 getInitialState: function () {
 return {items:

    […]};
 },
 render: function () {
 return (
 <div>
 <h3>TODO</h3>
 <TodoList items={this.state.items} />
 </div>
 );
 }
 });
  54. Getting the data in there

  55. PHP Backend JSON React Frontend

  56. PHP Backend JSON React Frontend

  57. PHP Backend JSON React Frontend

  58. PHP Backend JSON React Frontend

  59. Problematic

  60. Remember

  61. Self-contained

  62. Reusable

  63. PHP Backend JSON React Frontend

  64. PHP Backend JSON React Frontend

  65. PHP Backend JSON React Frontend

  66. Overfetching

  67. Underfetching

  68. Solutions

  69. Flux Datastores

  70. PHP Backend React Frontend Datastores

  71. GraphQL https://gist.github.com/wincent/598fa75e22bdfa44cf47

  72. Requirements

  73. Profile Publications Publication Publication Publication AboutMe LeftColumn Image Institution Menu

    React Frontend
  74. Profile Publications Publication Publication Publication AboutMe LeftColumn Image Menu PHP

    Backend Request Response Institution
  75. LeftColumn Image Menu React Frontend

  76. LeftColumn Image Menu Request Response PHP Backend

  77. Remember:  self contained

  78. Profile Publications Publication Publication Publication AboutMe LeftColumn Image Menu Account

    Account Account Account Account Publication1 Publication2 Publication3 Institution
  79. Do not fetch data directly

  80. Sssssssllllooooowww

  81. Require stuff

  82. http://www.infoq.com/presentations/Evolution-of-Code- Design-at-Facebook/

  83. None
  84. Widget Widget Widget Widget Preparer Resolver Resolver Services Connector Interfaces

    Connector Implementations
  85. Widget Widget Widget Widget Preparer Fetch Requirements Resolver Resolver Services

    Connector Interfaces Connector Implementations
  86. Widget Widget Widget Widget Preparer Resolver Resolver Services Connector Interfaces

    Connector Implementations Batch requirements and pass them to resolvers
  87. Widget Widget Widget Widget Preparer Resolver Resolver Services Connector Interfaces

    Connector Implementations Call Services as effective as possible (Multi-GET,...)
  88. Widget Widget Widget Widget Preparer Resolver Resolver Services Connector Interfaces

    Connector Implementations Attach fetched data to Requirements and pass them back to the preparer
  89. Widget Widget Widget Widget Preparer Resolver Resolver Services Connector Interfaces

    Connector Implementations Distribute fetched data to the widgets that required it
  90. public function collect() {
 return [
 new EntityRequirement(
 'account',
 Account::class,


    ['id' => $this->request->get('id')]
 ),
 ];
 }
  91. Request Cache

  92. Multi-GET

  93. Futures

  94. Data dependencies within a widget

  95. => Callbacks

  96. public function collect() {
 return new RequirementCollection([
 new EntityRequirement(
 'account',


    Account::class,
 ['id' => $this->request->get('id')]
 ),
 ], function() {
 return new RequirementCollection([
 new ServiceRequirement(
 'scienceDisciplines',
 AccountService::class,
 'getScienceDisciplines',
 ['account' => $this->account]
 )
 ]);
 });
 }
  97. PHP 5.5 Generators http://php.net/manual/de/language.generators.overview.php

  98. public function collect() {
 yield [
 new EntityRequirement(
 'account',
 Account::class,


    ['id' => $this->request->get('id')]
 ),
 ];
 
 yield [
 new ServiceRequirement(
 'scienceDisciplines',
 AccountService::class,
 'getScienceDisciplines',
 ['account' => $this->account]
 )
 ];
 }
  99. PHP 5.4 Traits http://us1.php.net/trait

  100. yield [
 serviceRequirement( 'job',
 JobService::getCall()->getById(248)
 )
 ];

  101. trait ServiceRequirementFactory {
 
 /** @return $this */
 public static

    function getCall() {
 return new ServiceRequirementFactoryProxy( static::class );
 }
 }
  102. class ServiceRequirementFactoryProxy {
 
 private $className;
 
 public function __construct($className)

    {
 $this->className = $className;
 }
 
 public function __call($name, $arguments) {
 return [
 'serviceClass' => $this->className,
 'methodName' => $name,
 'arguments' => $arguments
 ];
 }
 
 }
  103. Data dependencies between Widgets

  104. Assembling the tree

  105. Profile Publications Publication Publication Publication AboutMe LeftColumn Image Menu PHP

    Backend Request Response Institution
  106. Widget Requirement

  107. Prefills

  108. Profile Publications Publication Publication Publication AboutMe LeftColumn Image Menu Institution

  109. class PublicationKeywordSearch {
 
 public $publications;
 public $publicationListItems = [];


    
 public function collect() {
 yield [
 serviceRequirement(
 'publications',
 PublicationService::getCall()->getByKeywords(
 ['virology', 'cancer'], 10, 0
 )
 )
 ];
 foreach ($this->publications as $publication) {
 yield new WidgetRequirement(
 'publicationListItems',
 PublicationItem::CLASS,
 [ 'publicationId' => $publication->getId() ]
 );
 }
 }
 }
  110. class PublicationItem {
 
 public $publicationId;
 public $publication;
 
 public

    function collect() {
 yield new RequestDataRequirement('publicationId');
 yield [
 new EntityRequirement(
 'publication',
 Publication::class,
 ['id' => $this->publicationId]
 )
 ];
 }
 }
  111. Profile Publications Publication Publication Publication AboutMe LeftColumn Image Menu Institution

  112. Institution Profile Publications Publication Publication Publication AboutMe LeftColumn Image Menu

  113. Profile Publications Publication Publication Publication AboutMe LeftColumn Image Menu Institution

  114. Profile Publications Publication Publication Publication AboutMe LeftColumn Image Menu Institution

  115. Profile Publications Publication Publication Publication AboutMe LeftColumn Image Menu Institution

  116. Profile Publications Publication Publication Publication AboutMe LeftColumn Image Menu Institution

    DONE
  117. Getting the data to React.js

  118. public function getData() {
 return [
 'key' => 'value',
 'other'

    => 'data',
 'number' => 42,
 'subComponent' => $this->subComponent,
 'someArray' => [1, 2, 3],
 'subComponentList' => [ $this->items[0], $this->items[1] ]
 ];
 }
  119. {
 "component": "ParentComponent.jsx",
 "id": "ParentComponent-1234",
 "data": {
 "key": "value",
 "other":

    "data",
 "number": 42,
 "subComponent": {
 "component": "SubComponent.jsx",
 "id": "SubComponent",
 "data": { "boolValue": true }
 },
 "someArray": [1,2,3],
 "subComponentList": [
 {
 "component": "ListItem",
 "id": "ListItem-444",
 "data": { "title": "444s title“ }
 },
 {
 "component": "ListItem",
 "id": "ListItem-555",
 "data": { "title": "555s title“ }
 }
 ]
 }
 }
  120. var React = require('react');
 var SubComponent = require('SubComponent');
 var ListItem

    = require('ListItem');
 
 module.exports = React.createClass(
 {
 displayName: 'ParentComponent',
 render: function () {
 var items = [];
 
 for (var key in this.props.subComponentList) {
 if (this.props.subComponentList.hasOwnProperty(key)) {
 items.push(
 <li>
 <ListItem {...this.props.subComponentList[key]}></ListItem>
 </li>
 );
 }
 }
 
 return (
 <div className="{this.props.key}">
 <h1>{this.props.other} Hello PHP {this.props.number}</h1>
 <SubComponent {...this.props.subComponent}></SubComponent>
 <ul>{items}</ul>
 </div>
 );
 }
 }
 );
  121. var React = require('react');
 
 module.exports = React.createClass(
 {
 displayName:

    'ListItem',
 render: function () {
 return (
 <div>
 {this.props.title}
 </div>
 );
 }
 }
 );
  122. Server side rendering

  123. SEO

  124. Better user experience

  125. Several approaches

  126. http://pecl.php.net/package/v8js V8js

  127. Request HTML Preparer libV8 React PHP process Data array Rendered

    HTML string
  128. NodeJS proxy

  129. Request HTML Preparer nodeJS Proxy React PHP process Data JSON

    Rendered HTML string
  130. NodeJS service

  131. Request HTML Preparer nodeJS service React PHP process Data JSON

    Rendered HTML string
  132. Bundling

  133. http://webpack.github.io/

  134. None
  135. Whoa

  136. This looks awfully complicated to debug

  137. None
  138. None
  139. None
  140. None
  141. None
  142. Benefits

  143. Enables developers to only focus on their components

  144. Rapid prototyping

  145. Easier Refactoring

  146. Easy re-using of components

  147. Error Handling

  148. Profile Publications Publication Publication Publication AboutMe LeftColumn Image Menu Institution

  149. Profile Publications Publication Publication Publication AboutMe LeftColumn Image Menu EXCEPTION

    Institution
  150. Profile Publications Publication Publication Publication LeftColumn Image Menu Institution

  151. Experiments (A/B Testing)

  152. Feature toggles

  153. Profile Publications Publication Publication Publication AboutMe LeftColumn Image Menu Institution

  154. Profile Publications Publication Publication Publication AboutMeNew LeftColumn Image Menu Institution

  155. Caching of Components

  156. Profile Publications Publication Publication Publication AboutMe LeftColumn Image Menu <esi:include

    src="..." /> Institution
  157. Load components asynchronously

  158. Profile Publications Publication Publication Publication AboutMe LeftColumn Image Menu <div

    id="placeholder"></div> <script>loadWidget('/aboutMe', function(w) { w.render({ replace : '#placeholder' }); })</script> Institution
  159. Conclusion?

  160. Think about your architecture

  161. Refactor and make it better continuously

  162. Frontend and backend are part of the same application

  163. Don't rewrite your whole codebase in one go

  164. https://joind.in/13375

  165. http://twitter.com/BastianHofmann http://lanyrd.com/people/BastianHofmann http://speakerdeck.com/u/bastianhofmann mail@bastianhofmann.de