Limitations of the monolithic Facade
Sorry if this is a broad topic, but during my current downtime to develop some stuff, I ran into serious restrictions with RedBean, came to a number of conclusions and would love to discuss them with other users.
I have two major use cases right now:
Deploying RedBean across domains
The Problem
The monolithic Facade works well if you're developing a monolithic app. Meaning: a single database in a single domain (speaking in DDD terms). What I ran into was that I have multiple interdependent and interacting domains.
I have one app - hotbox
(a package creator similar to the packaging part composer) - that creates a main database for cross-project information and a further database for each project, all of which have their own logging and so forth. Then I use hotbox
in a client-facing server app, mangrove-server
which also has its own tables, including logging and so forth.
I do not want to have both apps speak to the same database because that would mean that I have to either use prefixes or implement tons of namespacing within my tables. Also - when I kill a project in hotbox
, I can get rid of the database instead of expensive sorting-out operations.
First, cumbersome solution
What I did initially was to use a single instance of RedBean and implement a database switcher. That only gets you so far, though, because you end up switching databases a lot. The bandaid that I used was that the only way that applications talk to eachother was through an API facade with static methods like so:
public static function registerPackage( $package, $branch )
{
$current_db = R::$currentDB;
R::selectDatabase(self::$config->database->name);
// Business Logic goes here
R::selectDatabase($current_db);
return true;
}
But this only solves matters across applications (and it's highly debatable how elegant the solution is), not within an application.
Current Solution
What I needed here was instances, plain and simple. So I'm currently writing daviddeutsch/redbean-instance to make that happen. Right now, it's a facade wrapper that registers a facade as an instance and implement a __call() function that relays method calls to static method calls (more below under 'downsides').
Also note that I wrote further rb plugins and in those plugins, I had to inject a facade parameter to the plugin static method, like so:
public static function prefix( $prefix, $facade=null )
{
if ( is_null($facade) ) {
$class = get_class(R::$writer);
} else {
$class = get_class($facade::$writer);
}
That's the only way I can ensure that I end up modifying the correct facade - again a restriction of a monolithic facade to begin with.
Downsides of the current approach
I had to write a translation function from static to regular method calls since:
$r2db2 = R::instance();
// Works
$project = $r2db2::dispense('project');
// --- MyApp.php ---
MyApp::deploy();
class MyApp
{
public static $db;
public static function deploy()
{
self::$db = R::instance();
}
}
// --- MyProject.php ---
class MyProject
{
public function stuff()
{
// Nope.
$user = MyApp::$db::dispense('user');
// Yup, but: eww.
$user = MyApp::$db->dispense('user');
// Also completely breaks Rx:
$user = MyApp::$db::_('user'); // Nope again
// I can make _() work, but again, eww:
$user = MyApp::$db->_('user');
// FindHelper is broken though, with no recovery:
$user = MyApp::$db::$r->user->one()->find(); // *shudders
}
}
So, the bottom line is: This makes Facade calls mad uglies.
Of course, I can (and probably will have to) convert the $x static in Rx to a property, but I'm jumping through hoop after hoop. Will have to rethink the Rx design.
Likely new approach
It appears, though, that I will have to copy most of the facade into a new class that emulates most of the behavior of the Facade (see below for further discussions on the concept of the facade).
This also solves the other problem I currently have deploying RedBean with clients is that I'm still running into some with PHP 5.2. So for them, __callStatic() isn't an option anyhow, killing plugins.
Deploying RedBean in a CMS context
The Problem
Usually, a CMS context means a single database, so that's all fine and dandy. But you also have lots of prefixes - one for the cms itself and one for the component that you're working on.
Current Solution
Right now, I have written to get around this with the daviddeutsch/redbean-prefix plugin
Downsides
Prefixes obviously have lots of downsides, but that's the nature of the beast and it's not in my powers to change that.
Broader ruminations on the viability of the Facade
To be quite honest, using Redbean "in the wild" (meaning: painful, but unfortunately non-negotiable situations) shows the weak points of a Facade approach pretty quickly. I still like the Facade a lot for quick and dirty work, but the more I think about it, conceptually, the more I see the Facade as a specialized version of an Instance.
My current conclusion is: Maybe a RedBeanPHP\Instance
should be part of Redbean, with RedBeanPHP\Facade
being a wrapper for that.
The biggest problem, of course, is that the syntax between static and instance calls are different, '->' instead of '::'. That means your code becomes less portable across implementations.
Furthermore: design of plugins
I've looked at a couple other plugins during my development and mostly what I see is that people write a R::ext() call straight into the class file. That's not really instance safe. Might not matter that much if you always want to have all your extensions, but can be problematic (let alone wasteful) as soon as you're in a CMS context where apps are talking to eachother, each with their own extensions.
In my opinion, a plugin should only supply the class, not inject itself into the Facade automatically.