Hi everybody, I hope somebody can help me out with the following.
Yesterday, I've searched the web for information on a thing called "Memoization" in PHP. I'm trying to port a RubyOnRails application to Laravel and in that RubyOnRails application, I created classes to store data that (hardly) change (if they do, I have to change the application's code and redeploy). Thinks of things like countries, supported languages for the application, order statuses, etc.
To keep the post simple, let's say the classes (which I call "Enum" classes) only store a code. The name is derived via Laravel's I18n mechanism.
I've created a base class (BaseEnum) that defines methods to work with these "enums". Then, I created subclasses for each individual case. For simplicity's sake, again I will only show the bare minimum of what I'm trying to deal with.
namespace App\Enums;
class BaseEnum
{
protected $code;
// Subclasses can set this to false to avoid sorting by the all() method.
public static $sort = true;
// In the real application, other things are possible as well, but this is
// just to keep it simple and demonstrate the problem.
public function __construct($code) {
$this->code = $code;
}
// Returns an array of enum instances. The results are sorted by their name
// unless a subclass sets the static variable $sort to false.
public static function all() {
// Convert the string elements of the objects() method to enum instances.
// *** THIS IS THE LINE THAT I WANT TO HAVE MEMOIZED!!! ***
$memo = collect(static::objects())->map(fn($i) => new static($i));
return (static::$sort ? $memo->sortBy(fn($i) => $i->name()) : $memo)->values();
}
public function code() {
return $this->code;
}
// Retrieve the translated name via the I18n mechanism of Laravel.
public function name() {
return __(static::translationKey() . '.' . $this->code());
}
// Subclasses must implement this method and return an array of strings.
public static function objects() {
die('Subclasses must implement this method!');
}
// Returns the translation key, which is derived from the class' name. Eg. if
// the class is LanguageEnum, it returns "enums.LanguageEnum".
public static function translationKey() {
return 'enums.' . (new \ReflectionClass(static::class))->getShortName();
}
}
A LanguageEnum class can then be defined like this:
namespace App\Enums;
class LanguageEnum extends BaseEnum
{
public static $sort = false;
public static function objects() {
return ['en', 'nl', 'de', 'fr'];
}
}
The code of BaseEnum shows one line that I would like to "memoize". In Ruby the all() method would look like this:
def self.all
@all ||= objects.map { |i| new(i) }
# sort() is a class method (you don't need brackets in Ruby if there are no
# arguments)
sort ? @all.sort_by { |i| i.name } : @all
end
That first line does "all the magic". Without getting to technical, @all is an instance variable, but if called in a class method (def self. does that), it becomes what I call a class instance variable. The value of that variable is different for each subclass, so LanguageEnum, CountryEnum, OrderStatusEnum would all have their own values for this (not sharing it with each other).
The ||= operator is like using $this->all = $this->all ?? ... in PHP. This is what makes the memoization work. In RubyOnRails, these Enum classes are loaded during start up and maybe sometimes more later, but certainly not for every request.
I tried to port this using static variables, but I noticed that the values are shared with each subclass. I'm afraid there's no "simple" solution for this, so I also looked into storing the results in an "application wide cache", but I'm very new into Laravel, so I don't know (yet) how to do this.
One more thing. You probably might be wondering why I do this, since there's not so much "calculation" going on in the all() class method. In reality, a bit more happens. To me, it seems to be fair to avoid the constant retrieval of the translated names. For now - since I have no solution - I do that. So each time the all() class method is executed, all elements from the objects() class method are converted to instances and sorted (if necessary).
I hope this all made some sense, I tried to give a real-life example for a more generic problem. Believe me, I use this "memoization" all over the place. I appears I am one of the few to do this, since there's not much information on the web for it, certainly not for PHP.