Eric Escalante Blogging gingerly

Isomorphic React apps in PHP via dnode - 4

Part 4 - Rendering from PHP with dnode

Or Ruby, or Java. Heck, even perl! Our proud little app can integrate with any language that has a dnode implementation!

When installing the php dnode package with composer, I was surprised to find installed a React dependency that actually has nothing to do with facebook’s! It seems to be a great library for evented i/o for php.

Also, when I first attempted this experiment, I tried using Facebook’s php library based on v8 or straight php v8 but it soon became obvious that another solution was needed.

In our node server

 1 dserver = dnode({
 2   renderIndex: function(remoteParams, remoteCallback) {
 3     var router = Router.create({
 4       location: '/developers/page/' + remoteParams.page,
 5       routes: routes
 6     });
 7     return router.run(function(Handler, state) {
 8       gutil.log("Serving to PHP via " + dnodePort);
 9       var component = state.routes[1].handler;
10       return component.fetchData(state.params, function(err, data) {
11         var reactOutput = React.renderToString(React.createElement(Handler, {
12           data: data,
13           params: state.params
14         }));
15         return remoteCallback(getHtml(reactOutput, data, state));
16       });
17     });
18   }
19 });
20 
21 dserver.listen(dnodePort);
22 
23 gutil.log("dnode: Listening on port " + dnodePort);

Explanation: These lines come after the declaration of our getHtml function in our server script (around line 48 over there). To begin things, we declare the name of the function we want to expose via dnode, in this case renderIndex. When the php side of dnode calls, it will provide the parameters and a remote function to be invoked as a callback once we’re done.

Inside our renderIndex function, on line 2, we’ll perform the now familiar task of getting a route handler for /developers/page/ (appending the page number sent by php). Then we go ahead and invoke the handler’s fetchData and use the resulting data to have React.renderToString provide the final html. Finally on line 18 we invoke the remote callback with the output of our handy getHtml function. Bam!

PHP index script.

 1 <!DOCTYPE html>
 2 <html lang="en">
 3   <head>
 4     <title>Sample PHP page rendering Node/ReactJS</title>
 5     <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
 6   </head>
 7 
 8   <body style="background:#FAFAFA;padding:20px">
 9     <div class="container">
10       <div class="jumbotron">
11       <h1>Sample PHP page</h1>
12       <p>The content below is rendered server-side with PHP/node/react via dnode.</p>
13       <p>Subsequent pagination is done with react client-side via Ajax.</p>
14       <p>This could be a Drupal, Wordpress, or any other kind of PHP app :)</p>
15     </div>
16 
17     <div class="panel panel-default" style="padding:20px;">
18 <?php 
19   require 'vendor/autoload.php';
20 
21   $path =  explode('/', $_SERVER['REQUEST_URI']);
22 
23   $options  = array('page' => array_pop($path));
24   $loop = new React\EventLoop\StreamSelectLoop();
25   $dnode = new DNode\DNode($loop);
26   $dnode->connect(3001, function($remote, $connection) use($options) {
27     $remote->renderIndex($options, function($result) use ($connection) {
28         echo $result;
29         $connection->end();
30     });
31   });
32   $loop->run();
33 
34 ?>
35       </div>
36     </div>
37   </body>
38 </html>

Explanation: The markup is just a simple html page, but close your eyes for a second and picture this implemented properly on a large, living app.

The magic starts at line 26, where we tell our dnode php instance to connect to port 3001, the one we defined in our node server script as dnodePort = 3001. This gives us a $remote object from which we can make calls to functions implemented on the other side of this funny wormhole, back in node.

So, when we do $remote->renderIndex that renderIndex is the function we just defined on our server script a couple of paragraphs above! When I saw this running the first time my mind was totally blown (away)! - yes, I know I need to go out more.

Next, we just pass the current page inside an $options array and get back a $result variable containing the output of the node app, including a link to our bundle.js that includes the browser-side react-routing code!

Our PHP app now serves pre-rendered react components! Once the page is ready, all user interaction is handled by the react components client side, communicating via ajax to the node app. Note that we can send the response from our node app in array so we can have body and head elements separately (like open graph tags, for example).

We made it!

This ends this short series and I hope it was fun and useful for people learning node, gulp, react, and curious about remote procedure calls, etc.

Don’t forget to check out the repo and get in touch with suggestions, questions, or complaints about my cat sneaking into your balcony and causing mischief!