Sharing state in a Symfony hybrid with Twig, React & any JavaScript/PHP apps
A while back I was working on an application where the back end is a full stack Symfony application. The front end had some rich functionality built with React. I was working on decoupling the front end completely by having the Symfony app serve only as an API backend.
I eventually abandoned this approach because it added complexity and yielded few benefits in that case. I still wanted to keep working with these two applications fluent, so I decided to share the state of the object via a dedicated object.
For that case it worked out fine, so I thought I would share the simple concept via a prototype implementation. The prototype is built with Symfony, but the simple concept can be used anywhere in PHP and why not other languages as well.
Prototype application description
The prototype application itself is deliberately limited in scope. It is a simple application that displays apartment listings from a relational database.
You can choose a few options and on the front page the app uses an API to add load data asynchronously.
To add some diversity to the application and not make it an xyz.js showcase, I implemented the front end functionality with three simple JavaScript approaches:
- Using the React view library
- Using the Vue.js view library
- Using plain JavaScript built with TypeScript
The implementations are very bare bones and are not in themselves very useful and definitely need a supporting client side state management library like MobX or Redux, but they can provide insight into how the three approaches work.
The whole application is available on GitHub, so in case you're interested in these front end technologies, you can take a look at the JavaScript directory of the repository.
Sharing state between Twig and client side apps with an object
At the heart of the application is a POPO (Plain Old PHP Object) that defines the properties that should be sent to the template and the JavaScript application:
This just contains properties like the apartments, sorting, etc. and in implements the JsonSerializable interface so we can serialize it later. The AppState objects are created in controller actions:
The apartments are populated from a Doctrine ORM repository and other properties should be set on whatever you desire at the state when the page is loaded. In the end we'll pass the Twig templating engine the object as well as the serialized state.
The Twig template itself loops through apartments and prints out the views with standard syntax, just next to what happens to be the root of our Vue.js root node (we will focus on Vue.js for brevity):
Next up in the baselayout Twig we output the serialized state to a JavaScript variable and initialize our Vue.js application:
Once this is complete then upon pageload the server renders the Twig view, but the front end application shares the same data and with Vue templating even the syntax looks very similar:
The simple JSON API is powered by the same exact state object, so I can already know what my endpoints will return:
And that's pretty much all there is to the prototype, really.
Conclusion
If you've read this far you might wonder that what is the point with all of this? So much complexity for so little benefit? The way I see it is that this sharing of a state object can reduce friction on working with PHP-JavaScript hybrid applications by removing guessing variable names and inventing new data structures.
For example if I were to add a new field to my Doctrine entity to an app that partly works via Twig, partly via Vue.js and partly via API, I would only need to take these steps:
- Add field to entity
- Add output where applicable in Twig
- Add output where applicable in Vue.js templates
If I didn't share the same object I would need to make sure the properties are passed forward here and there, invent some new variable names and so forth. I feel approach makes things more predictable, at least when working on a project of limited scope and complexity.
For larger projects you could expand the approach by having more State objects for each bundles, for example. This will allow developers to have insight into what should (no mechanism enforces using this) be available in a front end initial state object, simply by peeking in the bundle's State directory.
As a bonus when working with TypeScript you could create a Symfony command to serialize your dynamic models to TypeScript Type Definition and dump them to state.d.ts files to have insight into the data structures at front end development time:
In addition to this you could improve upon the concept with Server Side Rendering of the JavaScript components and probably much more.The source code is available and you're free to study it and use it how you wish.
I would be very interested in hearing feedback on it as well as any weaknesses or limitations you might see with this approach.
Related links:
- A Symfony hybrid app sharing state object with Twig, React and Vue
- Universal Rendering in PHP/Twig could be done with the Angular 2 Template Compiler
- Testing React.js isomorphic rendering with php-v8js and the Symfony Microkernel
- What is TypeScript and why should I care?
- The JavaScript language reboot is done