Here at Lazul Agency, we work with a variety of architectures and languages every day. One of the most common stacks that both ourselves and our clients utilize is the (in)famous PHP + MySQL combo. While we do love working with PHP, there are definitely some limitations to this server-side language. Today we’re going to dive into the problem with real time data retrieval and how we use WebSockets to create bidirectional data streams in PHP.
___
What’s the issue with PHP?
PHP is a server-side language, a characteristic that makes it an ideal choice for many user-based applications (like WordPress, for example). However, one downfall of its server-side nature is the lack of intrinsic support for handling data in “real time”. Increasingly, modern web apps are utilizing real time data models to create a more seamless and perceptively quick experience for users.
In a traditional PHP web app, server-generated content is only refreshed after page reload. The interruption in user experience each time a reload occurs is making this approach less and less desirable as time goes on.
___
The value of real time data flow
An example of a real time data model is Slack, arguably the most popular enterprise messaging SaaS right now. When you send a message in Slack, the app doesn’t need to reload the view or re-establish any backend connections; the flow of data is continuous during the entire user session. Imagine if instead of that continuous session, the entire application window reloaded each time you sent or received a message – it’s obvious that this would make the app seem slow, disruptive, and nearly unusable.
___
So, what is a WebSocket?
A WebSocket connection is a bidirectional data stream that resembles a two-way tunnel. Data can be sent to the WebSocket server, and it can also be received by the client application (the “client” referred to below). The WebSocket connection is particularly advantageous because it can be kept open for long sessions.
To establish a WebSocket Secure connection (the preferred protocol for security), the client must be using an HTTPS session; WebSockets cannot be initiated over plain HTTP. Then, the client sends a connection request to a WebSocket server. Once the connection is established, the client sends a query or data object to the server for processing, and then requests the processed data or response to be sent back.
___
Our use case for PHP + WebSockets
Recently, we developed an internal changelog built on top of this very website’s administrative dashboard. The changelog helps us keep track of bug fixes, modifications to our core infrastructure, and new features all in one central location. The page is built entirely in PHP and Javascript, and the functionality is as follows:
- Users can read and write to the changelog
- The creator of a changelog entry can mark their entry as Resolved, Cancelled, or Reopened
- Users can comment on changelog entries in a threaded format
- Approved admins can mark any entry as Resolved, Cancelled, or Reopened – even if they did not create that entry
When we first built the changelog, we didn’t consider the need for any real time data retrieval or insertion. However, the issues became apparent within the first week of using it. Because we were using a simple HTML form with method='post'
, the page would reload after creating an entry in order to make the POST request. Not only was it inconvenient for the person inserting a changelog entry, but also for the other users trying to read the changelog. With this approach, each person would need to manually refresh the page to view the latest, most accurate list of entries. Clearly, we needed some way to refresh the entry list as soon as it was modified in the database.
This is where WebSockets come in. By establishing a WebSocket connection on top of PHP, we could send and retrieve data and populate the changelog based on that data. For our use case, we chose the PHP-WSS open source library. Here’s an example of how we would establish a connection using the library:
<?php
use WSSC\WebSocketClient;
use WSSC\Components\ClientConfig;
$client = new WebSocketClient('wss://lazul-example.com/route/ws/SomeToken12345', new ClientConfig());
// Now we have an open WebSocket connection!
Great, so now we have a WebSocket session – what’s next? Well now we need to tell it to process some data and send it back to our application. Let’s take a look at how we do that:
$client->send('{"service" : "mysql", "db" : "lazul_changelog", "stored_query_name" : "getChangelogEntries"}');
$entriesJSON = $client->receive();
// The actual MySQL query is stored on our WebSocket server, so we can reference it via its stored_query_name
Our WebSocket server has logic built-in to determine how to handle a request that we send it. Once it processes our request (within milliseconds), the data is returned to us in JSON format. In this particular query, the JSON consists of the metadata and content of each changelog entry.
To use this freshly returned JSON, we use Javascript to hotswap actual changelog entries into our default empty list. This allows the view to be updated instantaneously and without any page reloads – perfect!
___
Managing concurrent WebSocket sessions
Because the entire purpose of this implementation was to allow for simultaneous, real time collaboration on the changelog, it’s crucial that our WebSocket server can accept and maintain connections to multiple clients.
As of now, we have a maximum of 4 team members who can view the changelog at any time. However, if we expand in the future or allow third parties to view our changelog, we’ll need to determine how to scale the WebSocket server accordingly. In the future, we might look to load balancing with AWS Elastic Beanstalk, or even via network proxy.
___
The importance of error handling
By default, PHP gives us some flexibility if an uncaught exception or otherwise unhandled error is thrown. We have our website configured to hide all PHP errors and warnings on the frontend, and log them to a local file on the server.
Things are a little different now that we’ve introduced WebSockets to the changelog page; if an unhandled error or exception is thrown, the WebSocket connection will be closed automatically. This means that we need to be especially thorough in detecting and handling every type of error that could possibly occur on the page.
___
In conclusion
Thanks for reading our very first engineering blog post! We hope you learned something about real time data retrieval, WebSockets, and PHP in this post. As our internal infrastructure evolves, we’ll share the knowledge and experiences with you in this blog. We also hope to make this an open forum for discussion and tips for our fellow software engineers, so feel free to leave a comment with your thoughts and suggestions!