35
Revealing Module design pattern and when (not) to use it in PHP
One of the most common design patterns used in JavaScript is Revealing Module. Since JavaScript initially didn't have classic Java-like OOP system, Revealing Module concept was used instead to create simple objects and implement private-like object properties. Revealing Module also fits well into functional nature of JavaScript. (You can see example implementation of Revealing Module in JS on GitHub Gist.)
Since PHP includes OOP for very long time, it is quite non-idiomatic to replace classical OOP object handling with functional alternatives like Revealing Module. However thanks to great support of anonymous functions in PHP it is very straightforward to implement it. You can do it as an exercise to dig deeper into functional PHP programming or you can use it in your mainly procedural codebase which you don't want to mix with OOP paradigm, but still want to use some object-like approach.
For easier explanation, I will start with practical example - using Revealing Module in PHP to implement simple "rectangle" object. You will easily understand that
$rectangle
is a constructor function, which returns $rectangle0
- an array representing newly created object instance with public properties and functions on it. Local variables inside $rectangle
functions on the other hand act like private members.<?php
$rectangle = function (int $a, int $b) {
$name = "rectangle";
$is_square = function () use (&$a, &$b) {
return $a === $b;
};
$rectangle0 = [
"get_area" => function () use (&$a, &$b) {
return $a * $b;
},
"get_perimeter" => function () use (&$a, &$b, $is_square) {
return $is_square() ? (4 * $a) : (2 * ($a + $b));
},
"set_a" => function (int $a1) use (&$a) { $a = $a1; },
"set_b" => function (int $b1) use (&$b) { $b = $b1; },
"get_name" => fn() => $name,
"to_string" => function () use (&$rectangle0, &$a, &$b) {
return sprintf("%s: %s x %s", $rectangle0["get_name"](), $a, $b);
},
];
return $rectangle0;
};
$rectangle1 = $rectangle(2, 3);
$rectangle1["get_perimeter"](); // result: 10
$rectangle1["get_area"](); // result: 6
$rectangle2 = $rectangle(4, 4);
$rectangle2["get_perimeter"](); // result: 16
$rectangle2["set_a"](5);
$rectangle2["get_perimeter"](); // result: 18
More general scheme of Revealing Module pattern with explanation of certain parts in comments looks as follows:
// 1) $obj is function acting as CONSTRUCTOR
// - passed parameters $param1, $param2 can be later accessed by any private or public properties/functions defined in constructor
$obj = function ($param1, $param2) {
// 2) PRIVATE properties/functions are implemented as local variables
$private_prop1 = 10;
$private_func1 = function () use (&$obj0, &$private_prop1, &$param1, &$private_func1) {
// private function $private_func1 can access other private properties/functions
$private_prop1++;
// private function $private_func1 can access public properties/functions via $obj0 reference (see below)
$obj0["public_prop1"] += $param1;
// function $private_func1 is also able to call itself recursively via $private_func1 reference
if (false) $private_func1();
};
// 3) PUBLIC properties/functions are implemented as values in $obj0 array
// - $obj0 itself represents newly created object instance and is returned from constructor function
$obj0 = [
"public_prop1" => 20,
"public_func1" => function () use (&$obj0, &$private_func1, &$param2) {
// public function public_func1 can access other public properties/functions via $obj0 reference
$obj0["public_prop1"] += $param2;
// public function public_func1 can access private properties/functions
$private_func1();
},
];
return $obj0;
};
// 5) INSTANCE $obj1 will be created by calling constructor function $obj
$obj1 = $obj(10, 20);
$obj1["public_func1"]();
This snippet comprises roughly everything you need to know about Revealing Module.
When using Revealing Module in PHP you should specifically be careful about
use (...)
construct, which needs to declare all variables from the outer scope that your function needs to use. Especially ensure you declare variables by reference (use (&$private_prop1)
) instead of by value (use ($private_prop1)
), otherwise the variable value in the inner scope will not match future changes of the variable in outer scope and it also won't be possible to edit the variable from the inner scope.Finally, it would be very interesting to look at pros and cons of Revealing Module approach in comparison with traditional PHP OOP.
$obj1["functions_group1"]["function1"]();
: member function function1
has been placed into functions_group1
, which is just a nested array of functions).Constructor function is standard variable (Closure
object) and can be manipulated easily.
- You can store it in another variable, return it from a function or pass it as a parameter:
$foo = fn($rectangle) => $rectangle(4,5);
$foo($rectangle);
- Standard OOP class on the other hand is not a variable and you can pass it as a parameter only using its name obtained from
::class
constant:
$foo = fn($className) => new $className(4,5);
$foo(Rectangle::class);
This list is very extensive, that's why you should use Revealing Module only sparingly.
instanceof
or get_class()
is not possible.include
it manually.use (...)
construct is annoying and causes code bloat.I hope this article has inspired you to use functional programming in PHP more frequently, or even try out Revealing Module pattern in PHP or any other functional-ready language. For common work on PHP backend, you should probably stick to traditional OOP approach.
35