I have a backend service written in PHP and a client mobile app. I want to share the code of a model class that describes the payload of a push notification. How can this be done? Can I avoid code duplication?
As you may already know, there’s no direct way to share code implmenetation bewteen a backend and a client app if the two are written in different languages. However, there are strategies you can employ to minimize code duplication.
The naive approach
One such approach is to create a shared understanding of the data being passed between the two systems by using a common data format.
Let’s make an example using a class PushMessagePayload
:
we can serialize the payload to JSON (or another common data format) when sending from the server:
Suppose that the client app is written in dart/Flutter. We create a corresponding class that can decode from this JSON data:
When you receive the payload in Flutter, you can decode it using the factory method you defined:
This strategy allows you to maintain the structure and functionality of the PushMessagePayload in both systems without directly sharing code between them.
Automate model generation using Quicktype
For more complex objects, you may want to look into automatically generating data classes from a common schema using tools like Quicktype. You can create a json schema once, then generate data classes in multiple languages. Quicktype is available as a web app or command line tool.
Automate model generation using Protobuf
A more comprehensive solution for model generation is protobuf. Protocol Buffers is a very good way to share data structures and RPC interfaces across multiple languages. It is a language-agnostic, platform-neutral, extensible mechanism for serializing structured data.
Protocol Buffers are defined in .proto files which can be compiled by the Protocol Buffer Compiler (protoc) into source code usable by multiple different languages.
Here’s an example of how you might define your PushMessagePayload using protobuf:
syntax = "proto3";
message PushMessagePayload {
string message = 1;
int32 itemId = 2;
}
Then, you would use the protoc compiler to generate the data classes in your target languages.
In PHP:
protoc --php_out=. payload.proto
In Dart:
protoc --dart_out=. payload.proto
In PHP, the generated class would look something like this:
the generated classes come with built-in methods for serializing to and from JSON. You can use the serializeToJsonString()
method to convert your message to a JSON string.
Using Protocol Buffers would allow you to avoid manually redefining your data structure in each language, and would give you a robust, efficient method for serializing and deserializing your data. The Protocol Buffers library also includes methods for performing RPC, if you want to share not just data structures but also interfaces between your PHP server and Dart client. However, keep in mind that Protocol Buffers might be overkill if your use case is relatively simple and you’re only trying to share a few data structures.
Install protobuf dependencies and compiler
To add the protobuf runtime library to your PHP project, you can use Composer:
Composer will automatically handle the installation and will create or modify a composer.json file in your project directory, which keeps track of the project’s dependencies.
The Protobuf PHP runtime library provides Protobuf support to your PHP application, but it doesn’t include the protoc compiler that you’ll need to generate PHP classes from your .proto files. The protoc compiler is part of the protobuf project and can be downloaded from the protobuf GitHub releases page.
You can use composer’s scripts
feature to add custom commands that can be run using composer run-script <command>
. Here’s how you could add a script to run the protoc
command:
-
Install
protoc
on your system. This step is platform-dependent and typically involves downloading the appropriate binary or package for your operating system from the protobuf GitHub releases page. Ensure thatprotoc
is accessible from your system’sPATH
. -
Add a
scripts
section to yourcomposer.json
file that looks something like this:
This script will run the protoc
command to generate PHP classes from your .proto files. You should replace src/
with the path to the directory where you want to generate your PHP classes, and protos/
with the path to the directory where your .proto files are located.
- Now you can generate your protobuf PHP classes by running the following command:
This will execute the protoc
command that you specified in your composer.json
file, generating your PHP classes.
In Dart you’ll need to install protobuf compiler plugin:
Using a docker image
If you’re using a docker image to build your project, you may need to install the latest version of protobuf compiler. Here’s an example of Dockerfile:
And what about namespaces?
You can set the namespace for the generated PHP files from your Protocol Buffers (protobuf) .proto
files by using the php_namespace
option in the .proto
file itself.
Here’s an example of what your .proto
file might look like:
In this example, the php_namespace
option is set to MyNamespace\SubNamespace
. When you compile this .proto
file with the protoc
command, the generated PHP class for the MyMessage
message will be in the MyNamespace\SubNamespace
namespace.
Remember that backslashes in strings need to be escaped, so you have to write \\
in your .proto
file to get a single backslash in the resulting PHP namespace.
Also, note that the php_namespace
option is a file-level option, so it will apply to all messages in the .proto
file. If you want to use different namespaces for different messages, you have to define them in different .proto
files.