Instead of a git repo I think it could be resumed here.
So, the goal is to have this app structure:
├── app
│ ├── config
│ │ └── config.php
│ ├── Modules
│ │ └── ModuleA
│ │ └── Controllers
│ │ │ ├── admin
│ │ │ │ └── IndexController.php
│ │ │ │ └── OtherController.php
│ │ │ ├── front
│ │ │ │ └── IndexController.php
│ │ │ │ └── OtherController.php
│ │ │ └── other
│ │ │ └── OtherController.php
│ │ └── Models
│ │ └── Services
│ │
│ └── views
│ └── modulea
│ ├── admin
│ │ └── index // for indexController in admin
│ │ └── index.volt
│ │ └── anotheractioninIndexController.volt
│ └── front
│ │ └── index // for indexController in front
│ │ └── index.volt
│ │ └── anotheractioninIndexController.volt
Actually, I set each module as a git repo and I load them trought composer, outside of the app folder. But since the place in the directory only depends in terms of autoload it does not matter too much.
I set namespaces for each module. For example, moduleA have App\ModuleA
as base namespace. So:
App\ModuleA\Controllers
|_ App\ModuleA\Controllers\IndexController
|_ admin
|_ App\ModuleA\Controllers\Admin\IndexController
|_,,,,
|_ ...
App\ModuleA\Models
App\ModuleA\Services
We just need to set a router, a generic one like:
I wanted to do this kind of dispatching:
- /admin/modulea/list ==> App\ModuleA\Controllers\Admin\IndexController::listAction
- /admin/modulea/1/view ==> App\ModuleA\Controllers\Admin\IndexController::viewAction(element_id)
- /admin/modulea/1/subcontroller/2/view ==> App\ModuleA\Controllers\Admin\SubcontrollerController::viewAction(element_id, subelement_id)
- /modulea/other/1/view ==> App\ModuleA\Controllers\OtherController::view(1)
Using this I can manage to use the routes in similar way to a rest routes (still not 100% accurate but I didn't put too much time in it)
Notice that there is no namespace defined.
$router = new Router(true);
$router->setDefaultModule('modulea');
$router->add("/", array(
'module' => 'modulea',
'controller' => 'index'
));
$router->add("/:action", array(
'module' => 'modulea',
'controller' => 'index',
'action' => 1,
));
$router->add("/:module/:action", array(
'module' => 1,
'controller' => 'index',
'action' => 2,
));
$router->add("/:module/:action", array(
'module' => 1,
'controller' => 'index',
'action' => 2,
));
$router->add("/:module/([a-zA-Z0-9_-]+)/:action", array(
'module' => 1,
'controller' => 'index',
'element' => 2,
'action' => 3,
));
$router->add("/:module/([a-zA-Z0-9_-]+)/:controller/:action", array(
'module' => 1,
'element' => 2,
'controller' => 3,
'action' => 4,
));
$router->add("/:module/([a-zA-Z0-9_-]+)/:controller/([a-zA-Z0-9_-]+)/:action", array(
'module' => 1,
'element' => 2,
'controller' => 3,
'action' => 5,
'subelement' => 4,
));
$router->add("/:module/([a-zA-Z0-9_-]+)/:controller/([a-zA-Z0-9_-]+)/:action", array(
'module' => 1,
'element' => 2,
'controller' => 3,
'subelement' => 4,
'action' => 5
));
// third param is wasted, we don't need it
$router->add("/:module/([a-zA-Z0-9_-]+)/([a-zA-Z0-9_-]+)/([a-zA-Z0-9_-]+)/:controller/:action", array(
'module' => 1,
'element' => 2,
'subelement' => 4,
'controller' => 5,
'action' => 6,
));
$router->add("/:module/([a-zA-Z0-9_-]+)/([a-zA-Z0-9_-]+)/([a-zA-Z0-9_-]+)/:controller/([a-zA-Z0-9_-]+)/:action", array(
'module' => 1,
'element' => 2,
'subelement' => 4,
'controller' => 5,
'hiperelement' => 6,
'action' => 7,
));
// since I don't know how to use the grouped routes I'm repeating the same with each resource:
// Notice the resource param added, so we can tell the dispatcher that a subfolder is used
$router->add("/admin/:module/([a-zA-Z0-9_-]+)/:controller/:action", array(
'module' => 1,
'element' => 2,
'controller' => 3,
'action' => 4,
'resource' => 'admin'
));
//......
I still think that the phalcon router lacks many things for this way of doing things but... it works
Modules are registered as always:
$application->registerModules(array(
'modulea' => array(
'namespaceName' => 'App\ModuleA\Controllers',
'className' => 'App\ModuleA\Module',
'path' => $this->getDi()->getShared('config')->paths->modules_path.'ModuleA'.DS.'Module.php'
), // ....
);
We just need to tell the system wich namespace needs to load when inside a Module. Lets do it inside the Module.php
public function registerServices(DiInterface $dependencyInjector = NULL)
{
$namespace = 'App\ModuleA\Controllers';
$resource = null;
if (isset($dependencyInjector['router']->getParams()['resource'])) {
$resource = $dependencyInjector['router']->getParams()['resource'];
}
$namespace .= '\\'.ucfirst($resource); // adding the subdir as namespace route
$dependencyInjector['dispatcher']->setDefaultNamespace($namespace);
}
Now we need to load the adecuate views and layouts. This is a temporal workaround:
$di->getShared('eventsManager')->attach("dispatch:beforeDispatch", function ($event, \Phalcon\Mvc\Dispatcher $dispatcher) use ($di) {
$layout_dir = '..' . DS;
$di['view']->setLayout('index');
$layout_dir .= '';
$path_view = '';
if ($resource = $dispatcher->getParam('resource')) {
$path_view = $resource . DS;
$di['view']->setLayout($resource);
$layout_dir .= '..' . DS;
}
$dir = strtolower($di['router']->getModuleName()) . DS . $path_view;
$di['view']->setViewsDir($dir);
$di['view']->setLayoutsDir($layout_dir . 'layouts' . DS);
});
$di['dispatcher']->setEventsManager($di->getShared('eventsManager'));
And it's done. Tell me what you think. (It still have room for many improvements)
P.D. Of course you can call any Model or Service, etc from any model, it is just related to the autoloading. The only thing we do is to guide the dispatcher to load the good Controller based on the module namespace and mess a little with the view paths.
P.D. Creo que tenemos las mismas manías a la hora de organizar la estructura de directorios :p