We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

Phalcon is not trully modular framework - and how to solve it.

Disclaimer: All the above is my opinion. I have checked few apps out there that implemented MVC in phalcon but none of them suit my needs.

First and the biggest problem in phalcon about modularity is the lack of standard module structure. Lack of any automated way to load your modules. When checking docs, you find how to register module:

    public function registerAutoloaders(DiInterface $di = null)
    {
        $loader = new Loader();

        $loader->registerNamespaces(
            [
                'Multiple\Backend\Controllers' => '../apps/backend/controllers/',
                'Multiple\Backend\Models'      => '../apps/backend/models/',
            ]
        );

        $loader->register();
    }

Each module would need to have another loader class. This is highly redundant. Not to mention you need to provide paths.

Second thing how you actually register modules. There are multiple problems with how it is done:

$application->registerModules(
    [
        'frontend' => [
            'className' => 'Multiple\Frontend\Module',
            'path'      => '../apps/frontend/Module.php',
        ],
        'backend'  => [
            'className' => 'Multiple\Backend\Module',
            'path'      => '../apps/backend/Module.php',
        ]
    ]
);

And what does the registerModules does? Well, nothing (Phalcon\Mcv\Application does not override this): https://github.com/phalcon/cphalcon/blob/master/phalcon/application.zep https://github.com/phalcon/cphalcon/blob/master/phalcon/mvc/application.zep

    public function registerModules(array modules, boolean merge = false) -> <Application>
    {
        if merge {
            let this->_modules = array_merge(this->_modules, modules);
        } else {
            let this->_modules = modules;
        }

        return this;
    }

So what are those problems here? That initialization class of each modules is not a singleton, it is not called UNLESS router tells it to. If you check handle method of Phalcon\Mvc\Application, you will see that only the currently used module is being called. Other modules intialization classes are ignored. This is a serious problem. Imagine a simple scenario: You have auth module which handles user authentication and needs to bind beforeDispatch event to redirect user to login page if he tries to access some specific content. It is simply impossible with current phalcon implementation to do that. Modules should be autonomous, like in practically any other modern PHP framework.

My solution to this problem was to extend Application (and ConsoleApplication because of the same thing) and override registerModules method like this:

    public function registerModules(array $modules, $merge = null)
    {
        parent::registerModules($modules, $merge);

        /** @var View $view */
        $view = $this->di->get('view');
        foreach ($this->getModules() as $moduleName => $module) {
            $initClass = $this->getModuleClass($moduleName);
            $initClass->registerAutoloaders($this->di);
            $initClass->registerServices($this->di);

            if (method_exists($initClass, 'init')) {
                $initClass->init($this->di);
            }

            if (method_exists($initClass, 'registerEvents')) {
                $initClass->registerEvents($this->di);
            }

            $this->registerRoutes($moduleName, $module);
            $view->registerViewPaths($moduleName, $module);
        }
    }

As you can see I have added 2 methods that can be called as well. Currently initialization classes aka. Module.php (Init.php in my case) have only registerServices and registerAutoloaders. Frankly, they might have been merged into one moduleInit method.

If you ever need to get the module initialization class, which should be possible, you create function like this:

    /**
     * @param $module
     * @return \Phalcon\Mvc\ModuleDefinitionInterface|null
     */
    public function getModuleClass($module)
    {
        if (isset($this->moduleInitClass[$module])) {
            return $this->moduleInitClass[$module];
        }

        $mod = $this->getModule($module);
        $class = $mod['className'];

        return $this->moduleInitClass[$module] = new $class;
    }

Which forces them to become singletons.

This was done because I needed such functionality. To have fully autonomous modules, inside one directory.

What am i saying right now. Phalcon needs to have better module initialization process. The one that:

  1. Loads all modules classes and register: autoloaders, services, events etc.
  2. Is well aware of modules components such as models and controllers by default (with the ability to change it)
  3. Does know where modules resides in so that you don't need to provide paths (but you should have ability to change it)

I skipped views on purpose because it can be decoupled from your application, also here is the topic about views (so lets not talk about problems with View itself) https://forum.phalcon.io/discussion/17305/phalcon-with-completely-custom-view-system

So how a minimal module definition should look like?

$modulesDirectory = APP_PATH . 'modules/';
$modules = [
    'moduleName' => [
        'namespace' => 'Vendor\ModuleName'
    ]
]

And thats it, application should be able to resolve class initialization, make instance of it, keep it as singleton. Then register Autoloades (if needed), register services, and register events.

If you need more customization then

$modulesDirectory = APP_PATH . 'modules/';
$modules = [
    'moduleName' => [
        'namespace' => 'Vendor\ModuleName',
        'path' => APP_PATH . 'custom/path/to/your/module', //this should not point to a Module.php, initialization class can be autoloaded if you register namespace, original path that pointed to one file is redundant
        'className' => 'Init', //no point in providing full namespace, as it is inside module it can be appended to namespace ex. $module['namespace'] . '\\' . $module['className']
        'priority' => 20, //as phalcon doesnt load all modules it still should be possible to have some sort of priority loading mechanism
        'controllersDirectory' => 'MyAwesome/Controllers', // ability to customize controllers directory for example if you wish it to be somewhere deeper, by default it may be 'Controllers'
        'modeslDirectory' => 'MyAwesome/Models', // same as above but for models
    ]
]

I cannot provide a full code but I can provide some parts of it if needed. In my application all you need to do is to provide module.json inside APP_PATH /modules/// directories and they are being autoloaded with everything that is needed.

{
    "name": "auth",
    "enabled": true,
    "priority": 1
}

and thats it.

Here is how my app looks like: https://i.imgur.com/RR0cLz1.png

And it is working 100%. I am using Twig for views and not using default phalcon View so extending/including cross module is possible.

Well, perhaps you could submit a PR with Zephir code which will resolve those concerns and make Phalcon "truly" modular, at least one leap in that direction.

For me this is bad idea. I like how all modules are indepndent and making applications faster, it's stupid to initialize all modules if they don't used.

edited Nov '17

Well, perhaps you could submit a PR with Zephir code which will resolve those concerns and make Phalcon "truly" modular, at least one leap in that direction.

That might be some idea however i don't know zephyr that much, i can cooperate and provide my php code to someone willing to translate it into zephyr.

I like how all modules are indepndent and making applications faster,

They are not making application faster. Your statement is wrong on so many levels. If my application would be like default phalcon i would still need to register all the services and all the events in some place. I just split that initialization into meaninigful modules like auth, crud, admin, helpers etc.

it's stupid to initialize all modules if they don't used.

You don't get it at all. They are used, and they are doing something meaningful (adding services, using events), For all the other modules it would just creating one empty class.

Right now phalcon lets you basically split application into 2 modules: frontend and backend.

edited Nov '17

I think this is a poor naming decision and a matter of semantics. The functionality you are describing falls into the roles of Plugins in the Phalcon world.

Modules are decoupled blocks of code, I think of them as application sharding.

Since when module is decoupled blocks of code? In just about every modern framework a "module" does have access to events. Laravel? Yep there is a service, and you have option to "boot" your module: https://kamranahmed.info/blog/2015/12/03/creating-a-modular-application-in-laravel/

Symfony calls modules a bundles (which is fine): https://symfony.com/doc/current/bundles.html and yes, you can hook events there, without writing a custom code to initialize your modules.

Plugins? You mean like this? https://github.com/phalcon/invo/blob/master/app/plugins/SecurityPlugin.php

Then it falls short compared to true module.

Lets take a look at authentication/user example: Module name: UserModule (or AuthModule)

What it should be able to do?

  1. Encapsulate user model and all user-related models like remember token, confirmation token, roles
  2. Encapsulate auth and user related controllers like auth and profile controller
  3. Encapsulate services like Auth (which does the actual job of logging in customer) and Acl (which checks if user has access to specific content)
  4. Encapsulate routes specific to those controllers (doesn't matter if they are automatic or not)
  5. Hook events that will let module to restrict access to content for example beforeDispatch

Why is that important to have everything in one place? Because if I ever want to create application that will not need users or authentication at all I can simply remove module folder and with some minor template tweaks (removal of acl checks) application will be running in full open mode.

And again you cannot call this a "plugin", it is a fully fledged module. Which almost works in bare phalcon Application except for point 5. And that is because modules are not initialized, they do not have a possibility to hook any events, they don't have any init/boot/whatever function that is called regardless of which is the "current" module.

Another example: AdminActionLogModule - a module responsible of logging each admin user actions. Such module needs to hook every action (either before or afterDispatch). If phalcon would let that by default it would be pretty easy to write it in one place.

There are tons of examples of modules that simply needs to initialize on every request, or hook some global event.

Now there is another thing, check this repo: https://github.com/phalcon/mvc and search for setDefaultNamespace in file named Module.php

Why would you need to define default namespace in each module? That is a unnecessary redundancy. Why is module namespace not defined in module definition?

By knowing:

  1. directory of where modules are located
  2. Namespace of each module

We have access to every namespace and path that is required. Instead we have to provide className and path to "Module.php" (which is redundancy by itself). It is cool if we have option to customize paths, namespaces but that should not be required but optional.

What am i saying is that modules in phalcon are incomplete, just as if modularity was a new idea and never implemented to the end. And by modularity I mean a possibility to encapsulate whole code related to specific field.

What about CLI tools for module? There is no way to keep your tasks inside a module. For example I want to have a cli command inside Auth/User module that lets me create admin users in console. I fixed that problem in my application but that required me to rewrite ConsoleApplication and convert zephyr code from Phalcon/Cli/Console back to PHP, namely "handle" function. Why was that a problem? Because code had no idea where CLI task resides in, code needed to know what is the namespace of Cli task of each module.

So to sumamrize what IN MY OPINION is wrong in phalcon about modules:

  1. Modules has no way to initialize themselves.
  2. Dispatcher is not aware of module namespace, and you cannot set namespace in module definition
  3. Router has the same problem as dispatcher

The questions you raise are valid, Modules in Phalcon do seem a bit awkward to use. I've ended up heavily customizing them to suit my needs.

I think this topic is worth a github discussion. Changing the responsibility, or renaming Module is not possible (unless in a major?), so I guess a new class should be introduced to handle the functionality you are describing. Bundle is not used anywhere yet :]

That might be idea however I am not the one to decide. There would be 2 ways to handle this. Either make bundles completely separate of modules (which may be a bad idea) or make bundles like extended modules that work exactly like modules but has addtional features:

$bundlesDirectory = APP_PATH . 'modules/';
$bundles = [
    'bundleName' => [
        'namespace' => 'Vendor\BundleName'
    ]
]
$app->registerBundles($bundles);

Which would roughly translate to something like:

$modules = [
    'bundleName' => [
        'namespace' => 'Vendor\BundleName',
        'className' => '...',
        'path' => '...',
        'type' => 'bundle'
    ]
]
edited Dec '17

I am using my customization of multi-module setup in PCan, - which is behind parracan.org.au - Parramatta Climate Action Network. So it presents a home page of link records to other web pages, on other sites or its own. It has the default 'app' module, an 'admin' module , a mailchimp interface module, and a few other experments for site-ideas I tried but not followed through or sold on to anybody. My documentation is poor, but I have been coming round to learning to add some to the code, at least when I'm rewriting it for the second and third time. in this rewrite round of site initialization code, my goal was to reduce the amounts of unncessary router configuration for modules that are not going to run for a given URL. PHP efficiency is about figuring out how to systematically involve the least number of lines of script necessary to get the job done. Loading up routes for all possible modules for every request seems un-neccessary, especially if strict discipline on module seperation is followed, to avoid URL clashes. The other general kind of aim is to move control of the processing to the appropropriate module as early as possible.

Phalcon enforces module separation, especially in that normal 'forward' is done within the same module. A redirect produces another request.

First is to peek at the rewrite URL early, before creating a router object, and check the first part matches some /{module-name-or-alias}/ If it doesn't then I only need to setup routes for the default module, otherwise just the routes for the only named module. There is a global config, containing data for an associative array called 'modules' . The keys are either a module name, with values as useful and customizable module configuation, or the key is an 'alias' (also defined in routes for that module), which must have a value as the primary name of the module configuration. Given this 'keyed' module configuation, finding which module is just a simple lookup of the first part of the URL into the array. An alias match is also followed to its module configuration. Add the single module registration, and set up the Router with just the one modules routes, dispatch away.

I haven't actually got this version in production yet, or even checked them back to github, which I ought to do now, since I have been working out the bugs in other bits of flakey code exposed by the changes, and I was doing it as part of writing a proper website setup code, with database initialisation and assets and environment initialization. But these initialization parts seem to working well enough now. I partially integrated devtools and have been modifying migrations output, which needs work. There is too much stuff that needs to be automatically done, rather than by bug trip discovery and fix.

I think you missed the point. Module separation is not a good thing, and not even close to being a real modulatiry solution.

Loading up routes for all possible modules for every request seems un-neccessary

Routes are not a problem at all, you can cache them. In fact, building something before router that would look up the url would only slow down application unnecessarily. Unless you have many dynamic routes (and by many i mean many combinations, not url entities) like for example magento (multiple URLs for a single product) then you would need to think about other solution or a custom router.

The real problem lies within not being able to hook on global event, not being able to register a service within a module.

@pwilkowski yous point is so damn right! Thanks for sharing your great thoughts about this topic.