Open mrgrain opened 9 years ago
@Thoronador @Satans-Kruemelmonster @Mainclain
I started some experiments in a branch on my fork. At first I just wanted to create an easy system to auto load plugins/modules during runtime. (That's the reason for the branch's name.)
But soon I discovered there are deeper changes needed to have a proper implementation. Or rather mimicking those modern features to fit to the frogsystem is not a good idea, because it adds an other (soon to come) legacy layer and it's an unnecessary waste of time as well. So, what I did now, is starting to implement a smell, yet powerful DI container system. It's called frogsystem/spawn and I plan to release it as a separate package. Please have a look on the current changes https://github.com/frogsystem/fs2core/compare/master...mrgrain:feature-plugin-system and tell me your opinion.
The current pre-alpha includes a full working base container and mainly refactors the old Frogsystem2 class to use this system. Therefore I moved most of it's old the functionality into legacy packages. Those (and many more parts) will need replacement, to make it actual useful.
My plan for the near future consists manily of two parts: a) Implement the module auto running. b) Create one legacy module which contains all the old code. Parts of it can then be replaced by separate modules (as needed).
I'm very open for discussion! Please give any feedback or ask me any questions. Thx!
What you did there is a big step in the right direction. You added some dependency injection, make more use of oop and added phpDocs. But while you are working very hard to get all these things working, you miss something, I think: All of this is already done in very many frameworks out there. It would probaply be better to invest the programming-time with getting old code better than to build something from scratch that is actually there, along with many useful features like database abstraction layers, built-in routing support, model-view-controller (or some different design pattern - I'm sure everything you like is out there), event-driven-development-support and so on and so forth. I worked with the Zend Framework 2 and I am very happy with it. It is a mvc-framework which features all stuff mentioned above (except a dbal which can be added by plugging in Doctrine 2). Of course there are many other frameworks as well. There are symfony, yii and so on, which are great too, I am sure, but I didn't really work with them. So, you might consider using one of these than progamming everything on your own. They are well tested, carefully planned and under permanent development. Using a framework might result in the need to rewrite almost all of the frogsystem's code, but at by following this path this has to happen anyways, I think.
And there is another thing: I am not really sure about the code-concept of the frogsystem the way you are planning it with your own code (If you use Zend Framework, you are a bit limited, but in my opinion, it is worth it). How should the installation of new modules work (you plug them in, how do you update the database, how do you activate it, …)? How will the routing between modules and the URL work? How will the admin-cp fit into this system (I have seen some code about admin, but I am not sure if there is a plan…)? Wouldn't in be better to use factories for initializing an object rather than defining depenencies in the class itself (I am not really sure how this is meant when using dependency injection, but you have quite a lot of code just for initialization purposes)?
But I want to say a big thank you for the decision to get the frogsystem's code-basis to the next level. It in a necessary step, but I was afraid it would never happen.
Thank you very much for your long answer :) I will address the two topics you raised in two posts. This is the first one, the other one will follow tonight.
I know it would be better to use a framework. There are a couple of reason I don't and won't and one of them is surely my indefinitely stubbornness. :wink: I've worked with ZF2 and I wasn't happy with it. Same goes for Symfony which is basically an open source Zend clone. At the moment I'm doing a lot with Laravel, which I like much better and a lot of the new code is inspired from it (as well as from express.js and slim-php). The problem I see with all those frameworks: They are just to big and complicated. Plus some of them are emphasising design patterns that I wouldn't emphasises that much.
But the biggest concern I always have is transition: At the moment I just can't see a new Frogsystem based on a Framework released within reasonable time. I'd rather expect something around 3 to 5 years, because as you mentioned: Everything has to be redone. That is not helping any website and the upgrading has to be done as well. I'm not really willing to do all this (see below why). Maybe there is a way to 'flange' the old stuff into a framework. Maybe not. I wouldn't know how to do it for Laravel. The new code I'm writing has at least one big maxim: Old stuff has to work. Not matter what. This means a lot of components can still be used (e.g. Lang, Templates, search) Plus: I'm not going to write everything from scratch. For know, just the container basis. Maybe some other components as well, but I'm very happy to just composer install
Doctrine 2 for example. Or Symfony's Fileaccess components. Cause they're pretty awsome!
(So why am I so stubborn about this: It's open source and it's my free time. Building a bunch of CMS pages is not really fun. Implementing concepts from scratch is and at least I learn a lot from it. I would probably develop this stuff anyway just for fun! But, and this is very important: If one of you guys is keen to start (and lead) a 'fork' based on a framwork: I will contribute. I know of at least two attempts to do that. As far as I know both of them failed to mature to stage where extending would have been possible.)
Installing and updating modules will be handled by composer. I don't see any other convenient to do this at the moment. Maybe, it some distant future, there will be an interface in the backend to do this. Database stuff should be handled by the plugin it self, using provided tools (Doctrine2,Eloquent). Maybe there's a place for a defined migration process using an up/down pattern. But it's rather simple to implement by yourself.
There will be a Router (which actually already exists) to register your controller on. A fallback route will then handle routes by the old schema (looking for files in a specified folder). The admin panel is just a bunch of routes, which coincidentally share the same prefix (/admin). Also every module will bring it's own admin files.
Having a factory that is used anywhere else outside of the core classes means, that an other class is still heavily coupled with a part of the core: The factory itself (and maybe even how to instantiate an object) . You surely can do this and it's still better then most of the other patterns around (scary singleton!). But for real Dependency Injection you will have to go one step further. Therefor a class defines it dependencies in the constructor (or method) and just doesn't care what object it gets and how it was created. As long as it fits to the type the class asked for. If the type is an interface or contract - even better (design by contract). There will be just one central place where all the object instantiations happen. (Or rather one per module, if the module needs to define its own within its closed scope). Thats why there's so much initialization code in the Frogsystem2 class. But it's okay, cause this class is the very core of the system and will never be replaced by an other module.
I hope I could clarify some things. And thank you again for the reply. :smiley:
You did, thanks for that. I am glad, you considered my points before starting, although you decided not to use a framework. I was just afraid, you wouldn't think this through. I personally don't want to reinvent the wheel, but if you want to, go ahead!
Be that as it may, I will watch your new code-basis and if it is far enough to begin working on some of the old code improvements, I will start contributing again, I think. :)
Ok, now I downloaded the code an had a closer look on it. I have some other points I do not really understand.
First of all and the biggest one: What is meant by a container? The Frogsystem2-class is defined as a container (After inheriting from many other classes, but this is the base-class). From my point of view this might be a collection for other objects. But if this is so, why is there so much other stuff going on there (application-initialization, failure-management, …)?
Second, why do you use array-storage to store services (or however you call for example the router, session-handler, autoloader and so on in the Frogsystem2-class)? To me this is not a good way to store objects. You have no code-completion, they pop out of nowhere in other parts of the code (to me, this makes troubleshooting much more difficult: you have no proper definion of which object really should be and so on) and everybody in other parts of the appliction may relace the object however they want. In combination with your many "global $FD"-calls very difficult in terms of security, I think. Probaply you could help me out here. ;)
Which leads me to point three: The "global $FD"-calls are for backward-compability-reasons, right? If so, I wonder why you didn't use your dependency-injection in new code to get the FD-object (for example in LegacyRouter::route()). Is this just because you copy-and-pasted old code and wanted to get things working as fast as possible?
There are other questions concerning other things I didn't get by now, but I am sure it is because I didn't get that deep into the code. (I don't understand how this plugging-thing is supposed to work, but I will get those things on my own, I hope :D )
The "global $FD" will be replaced soon. As you figured out, that's just "get it running" code at the moment. The router should ask for the config service instead and only use the injected instance.
For the container: It's a plain Inversion of Control Container, implemented by Dependency Injection (constructor & parameter injection) as main pattern and spiced with some other patterns (e.g. using factory and strategy to create objects). It's a bit wicked, If you never have used it before. And probably this code is not the best to learn it (given there is no documentation at all).
We need internal array storage to enable identifiers containing namespaces. acme\package\coolmodule
can't be used as a property name because of the backslash. Code completion will only be an issue within your main application class. All other services (yes, that's how you call them) will should get there dependencies via arguments prefixed by the choosen contract. You could also use PHPDocs to annotate the assignment which should fix code completion in many IDEs.
There's also a convention that (internally used) core objects (like the router) of an app are not only available by there classname, but also available from shorthands ($app->router
$app->db
). This is commonly called aliasing, but there is still some work to do to make this smooth.
Regarding the security: Yes, in general a container allows anyone to set and manipulate objects. This is part of the concept. Maybe we need more checks here (e.g. if an ID was already defined it can only be overwritten by something that is also an implementation of a common parent) or a feature to actively guard some IDs after definition. But it's not a security risk. Any code running can do much worse than messing around with the container. Plus...
... Pluggables or ServiceProviders. You might have heard about the later term. It's a way to allow extensions(what ever that means) adding their services to the container. And it's a simplification. Instead of registering every single service, you'll just register a couple of ServiceProviders. They are a good way the extend your application if you're actually an application developer. They are not very handy if you want to load in actual front facing functionalities (like a module). Therefore we will use a more generic form: Pluggables.
(And now it's getting really wicked!)
As our container implements the delegated lookup feature (as standardized by container-interop) we have a amazing thing called PluggableContainer. A PluggableContainer is an app only responsible for its own domain (e.g. News). It does all the stuff the main app is doing: Register routes, set up controllers, configs or whatever. Except it doesn't care about resolving dependencies (like a database). It will simply use the Container::delegate()
method to delegate this to the main app container.( I'll soon come up with a code example for this.)
@Satans-Kruemelmonster @Thoronador A small update: I split up the code into different packages and it is now kind of stable to actually use and 'feel' the new package structure. So basically all the current code is part of a legacy package that will be replaced over time. What we know need is modern helper classes for config, database and views (including support for the style system). If those three parts are finished we will be able to create new packages future proof packages.
See https://github.com/frogsystem/frogsystem for the current main project and https://github.com/frogsystem/fs2legacy for the legacy package.
There are still some issues to solve, mostly on a architecture level, but we're getting there. :)
We all know that most of the code here is crap. It limits us in terms of development speed, flexibility and extensibility.
This issue is to discuss what the future of the frogsystem will look like.