diff --git a/src/core/operations/PHPSerialize.mjs b/src/core/operations/PHPSerialize.mjs
new file mode 100644
index 00000000..94f42cbb
--- /dev/null
+++ b/src/core/operations/PHPSerialize.mjs
@@ -0,0 +1,144 @@
+/**
+ * @author brun0ne [brunonblok@gmail.com]
+ * @copyright Crown Copyright 2023
+ * @license Apache-2.0
+ */
+
+import Operation from "../Operation.mjs";
+import OperationError from "../errors/OperationError.mjs";
+
+/**
+ * PHP Serialize operation
+ */
+class PHPSerialize extends Operation {
+
+ /**
+ * PHPSerialize constructor
+ */
+ constructor() {
+ super();
+
+ this.name = "PHP Serialize";
+ this.module = "Default";
+ this.description = "Performs PHP serialization on JSON data.
This function does not support object
tags.
Since PHP doesn't distinguish dicts and arrays, this operation is not always symmetric to PHP Deserialize
.
Example:
[5,"abc",true]
becomes
a:3:{i:0;i:5;i:1;s:3:"abc";i:2;b:1;}";
+ this.infoURL = "https://www.phpinternalsbook.com/php5/classes_objects/serialization.html";
+ this.inputType = "JSON";
+ this.outputType = "string";
+ this.args = [];
+ }
+
+ /**
+ * @param {JSON} input
+ * @param {Object[]} args
+ * @returns {string}
+ */
+ run(input, args) {
+ /**
+ * Determines if a number is an integer
+ * @param {number} value
+ * @returns {boolean}
+ */
+ function isInteger(value) {
+ return typeof value === "number" && parseInt(value.toString(), 10) === value;
+ }
+
+ /**
+ * Serialize basic types
+ * @param {string | number | boolean} content
+ * @returns {string}
+ */
+ function serializeBasicTypes(content) {
+ const basicTypes = {
+ "string": "s",
+ "integer": "i",
+ "float": "d",
+ "boolean": "b"
+ };
+
+ /**
+ * Booleans
+ * cast to 0 or 1
+ */
+ if (typeof content === "boolean"){
+ return `${basicTypes["boolean"]}:${content ? 1 : 0}`;
+ }
+
+ /**
+ * Numbers
+ */
+ if (typeof content === "number"){
+ if (isInteger(content)){
+ return `${basicTypes["integer"]}:${content.toString()}`
+ }
+ else {
+ return `${basicTypes["float"]}:${content.toString()}`
+ }
+ }
+
+ /**
+ * Strings
+ */
+ if (typeof content === "string")
+ return `${basicTypes["string"]}:${content.length}:"${content}"`;
+
+ /** This should be unreachable */
+ throw new OperationError(`Encountered a non-implemented type: ${typeof content}`);
+ }
+
+ /**
+ * Recursively serialize
+ * @param {*} object
+ * @returns {string}
+ */
+ function serialize(object) {
+ /**
+ * Null
+ */
+ if (object == null) {
+ return `N;`
+ }
+
+ /**
+ * Basic types
+ */
+ if (typeof object !== "object"){
+ return `${serializeBasicTypes(object)};`;
+ }
+
+ /**
+ * Arrays
+ */
+ else if (object instanceof Array) {
+ const serializedElements = [];
+
+ for (let i = 0; i < object.length; i++) {
+ serializedElements.push(`${serialize(i)}${serialize(object[i])}`);
+ }
+
+ return `a:${object.length}:{${serializedElements.join("")}}`
+ }
+
+ /**
+ * Objects
+ * Note: the output cannot be guaranteed to be in the same order as the input
+ */
+ else if (object instanceof Object) {
+ const serializedElements = [];
+ const keys = Object.keys(object);
+
+ for (const key of keys) {
+ serializedElements.push(`${serialize(key)}${serialize(object[key])}`);
+ }
+
+ return `a:${keys.length}:{${serializedElements.join("")}}`
+ }
+
+ /** This should be unreachable */
+ throw new OperationError(`Encountered a non-implemented type: ${typeof object}`);
+ }
+
+ return serialize(input);
+ }
+}
+
+export default PHPSerialize;
diff --git a/tests/operations/tests/PHPSerialize.mjs b/tests/operations/tests/PHPSerialize.mjs
new file mode 100644
index 00000000..fa6e87c5
--- /dev/null
+++ b/tests/operations/tests/PHPSerialize.mjs
@@ -0,0 +1,112 @@
+/**
+ * PHP Serialization tests.
+ *
+ * @author brun0ne [brunonblok@gmail.com]
+ *
+ * @copyright Crown Copyright 2023
+ * @license Apache-2.0
+ */
+
+import TestRegister from "../../lib/TestRegister.mjs";
+
+TestRegister.addTests([
+ {
+ name: "PHP Serialize empty array",
+ input: "[]",
+ expectedOutput: "a:0:{}",
+ recipeConfig: [
+ {
+ op: "PHP Serialize",
+ args: []
+ }
+ ]
+ },
+ {
+ name: "PHP Serialize empty object",
+ input: "{}",
+ expectedOutput: "a:0:{}",
+ recipeConfig: [
+ {
+ op: "PHP Serialize",
+ args: []
+ }
+ ]
+ },
+ {
+ name: "PHP Serialize null",
+ input: "null",
+ expectedOutput: "N;",
+ recipeConfig: [
+ {
+ op: "PHP Serialize",
+ args: []
+ }
+ ]
+ },
+ {
+ name: "PHP Serialize integer",
+ input: "10",
+ expectedOutput: "i:10;",
+ recipeConfig: [
+ {
+ op: "PHP Serialize",
+ args: []
+ }
+ ]
+ },
+ {
+ name: "PHP Serialize float",
+ input: "14.523",
+ expectedOutput: "d:14.523;",
+ recipeConfig: [
+ {
+ op: "PHP Serialize",
+ args: []
+ }
+ ]
+ },
+ {
+ name: "PHP Serialize boolean",
+ input: "[true, false]",
+ expectedOutput: "a:2:{i:0;b:1;i:1;b:0;}",
+ recipeConfig: [
+ {
+ op: "PHP Serialize",
+ args: []
+ }
+ ]
+ },
+ {
+ name: "PHP Serialize string",
+ input: "\"Test string to serialize\"",
+ expectedOutput: "s:24:\"Test string to serialize\";",
+ recipeConfig: [
+ {
+ op: "PHP Serialize",
+ args: []
+ }
+ ]
+ },
+ {
+ name: "PHP Serialize object",
+ input: "{\"a\": 10,\"0\": {\"ab\": true}}",
+ expectedOutput: "a:2:{s:1:\"0\";a:1:{s:2:\"ab\";b:1;}s:1:\"a\";i:10;}",
+ recipeConfig: [
+ {
+ op: "PHP Serialize",
+ args: []
+ }
+ ]
+ },
+ {
+ name: "PHP Serialize array",
+ input: "[1,\"abc\",true,{\"x\":1,\"y\":2}]",
+ expectedOutput: "a:4:{i:0;i:1;i:1;s:3:\"abc\";i:2;b:1;i:3;a:2:{s:1:\"x\";i:1;s:1:\"y\";i:2;}}",
+ recipeConfig: [
+ {
+ op: "PHP Serialize",
+ args: []
+ }
+ ]
+ }
+]);