I am in these days
toying around with the
slim branch in the
Revolution repository at GitHub.
First of all, a quick introduction to what we are trying to do there and what we are doing:
Revolution has since its very first version (as far as I know) had an architect where the
core class (named modX) is extending the xPDO class, e.i. the ORM library. In the slim branch we want to instead have
modX extend the Slim app class. We also change
xPDO to become a dependency, which will losen the coupling between the two. This is necessary to modernize the codebase to using Dependency Injection (DI) architectures.
Why the old "extending xPDO" causes problems:
In the current codebase (or 2.x if you like), you can do this:
$obj = $xpdo->newObject('modUser');
get_class($obj->xpdo); // the xpdo instance variable is an instance of xPDO
But, you can also do this (from within the modX class):
$obj = new modUser($this);
get_class($obj->xpdo); // the xpdo instance variable is an instance of modX
This leads to problems. Many places, the objects (such as
modResponse,
modRequest, and more) expects the modX instance to be available. Some classes can also be instantiated with both modX and xPDO passed in the constructor, such as modInputFilter and modOutputFilter. This is why I had to
remove the typehinting in the constructor for these classes. The modUser class also have
separated behavior defined if the xpdo variable is an modX instance.
What this means:
The tight coupling between the instances of various classes, and how they depend on the instance of modX has lead me to belive that Revolution 3.x can not be backwards compatible with earlier versions. The
MAB-04 goal says:
By refactoring MODX Revolution 3.x on top of Slim 3.x, MODX would instantly gain the benefits of an outstanding PHP microframework as a new foundation for future advancements and improve maintainability. By simply adapting Revolution's modX class as a Slim App instead of extending xPDO for this foundation, PSR-4 autoloading, proper dependency injection, PSR-7 HTTP messaging standards and the flexible middleware-centric architecture would automatically become part of the MODX core. Much backwards compatibility can be maintained while effectively jump starting the modernization of the existing codebase.
Emphasis mine.
I belive that this goal is unachievable. There is no way to get around the technical problems that follow the decoupling of modX and xPDO. I also belive that is will be difficult, if not possible to maintain much of the backwards compatibility either. Because the Revolution codebase does not adhere to common OOP best practices, the "internal" representation of objects and instances leaks everywhere. We can not just change modResponse to not have an instance of modX passed to it. Extras and project code depends on this instance and depends on methods available in the modX instance. If we change this to being xPDO, a LOT will break. This includes connectors, controllers and extending xPDO objects.
Some of this could be avoided if the variables were boxed, and we used getters and setters for various tasks. This way we could "cheat" various problems, but I am still not convinced this would solve the problem entirely.
We can for example see that a random extra (Collections used for example)
depends on the modx instance in its controller(s). This would not work if we go all the way with DI and extending the Slim app.
Because of this am I unsure how to proceed. Should we attempt to do this another way? Could serving the entire modX instance as a DI be a possible work around? This could make the existing code base work with more or less the current code, but the decoupling would get nowhere.
Overarching ideas:
Because we need to change so much of the original (current, 2.x) codebase, the changes will diverge so much between the slim branch (soon to be 3.x) and the 2.x branch that we will not be able to merge patches and changes from 2.x into 3.x in a very short amount of time. This is simply because too many changes in the 3.x branch are needed to get it to work with the Slim app approach.
Because of this, we can ignore keeping most of the files in the core/modx/model directory and we can start working almost exclusively on the files in the core/src directory. Essentially we can move the current files in the model directory into src and split up the classes, add namespaces and the works. Because BC is not possible anyways, there is no reason to keep all the code in the model directory intact, as we an not benefit from the 2.x patches and changes.
Alternative ideas:
Otherwise we can make modX a service and pass it around. However, this approach will not decouple anything between modX and xPDO, and we are more or less where we are today once done.
Things that needs to be decided/figured out:
While working on the slim branch I came across numerous stuff that needs to be decided or figured out. Firstly we need to decide if any instance of modX should be passed anywhere at all. As I assume this is NO we will need to change a lot of code, especially concerning requests, response, controllers, connectors, and processors. Otherwise we need to decide on another approach to keep these things working like they do.
We also need to split up the MODX\modX class into separate classes, many of which should work as typical utility classes. This includes methods for sanitizing etc. There is no need to keep this in the base class other than BC. We also need to decide if we should override __call for this functions and reroute them to the new classes. This could keep some BC for some of the functions.
I also need to figure out what the modX::getOption and xPDO::getOption methods do, and what their purposes are. Why are there two of these? The same goes for modContext. How is this implemented and how does its inherited functions work? I have yet to understand this.
I hope to get some comments and insight from other people (hopefully those who have played with the 3.x/slim branch) and/or other people in the MAB that know more details about the future roadmap for this project.