yii2yii-url-manager

How to logically combine two controllers in yii2?


I have module named dashboard and PlanningController inside. The controller has become too big and I want to split it into several separate controllers.

Now I have the following actions in PlanningController:

/dashboard/planning/purchase
/dashboard/planning/purchase-create
/dashboard/planning/purchase-update

/dashboard/planning/supplier
/dashboard/planning/supplier-create
/dashboard/planning/supplier-update

But I want to have 2 separate controllers PurchaseController and SupplierController which will be logically combined into a folder planning:

/dashboard/planning/purchase
/dashboard/planning/purchase/create
/dashboard/planning/purchase/update

/dashboard/planning/supplier
/dashboard/planning/supplier/create
/dashboard/planning/supplier/update

I read that Yii2 does not support subfolders in controllers. How do I merge two controllers into one folder?


Solution

  • Depending on what exactly you want to achieve there are multiple ways to handle this.

    Nested Modules

    You can use nested modules to structure your code. Create a planning sub-module inside of modules/dashboard/modules. Then in the dashboard's Module class add the nested module for example like this:

    namespace app\modules\dashboard;
    
    use app\modules\dashboard\modules\planning\Module as PlanningModule;
    use yii\base\Module as BaseModule;
    
    class Module extends BaseModule
    {
        public function init()
        {
            parent::init();
    
            $this->modules = [
                'planning' => [
                    'class' => PlanningModule::class,
                ],
            ];
        }
    }
    

    That way you can separate all code related to your planning controllers into its own sub-module. Also, it will help you avoid any potential conflicts in routes.

    Controller Map

    The property yii\base\Module::$controllerMap allows you to use controllers that doesn't match the default yii's naming and folder structure conventions. With that you can place your PurchaseController and SupplierController into folder modules/dashboard/controllers/planning then set the map in your module class like this:

    namespace app\modules\dashboard;
    
    use app\modules\dashboard\controllers\planning\PurchaseController;
    use app\modules\dashboard\controllers\planning\SupplierController;
    use yii\base\Module as BaseModule;
    
    class Module extends BaseModule
    {
        public $controllerMap = [
            'purchase' => PurchaseController::class,
            'supplier' => SupplierController::class,
        ];
    }
    

    If you use this approach and you want routes to contain the "/planning/" part you will have to set up specific url rules.

    Standalone Actions

    If you only want to split the code of PlanningController because it is getting too big, but you are ok with keeping it as single controller. You can extract the action code into standalone action classes. For example you can create PurchaseCreateAction class in modules/dashboard/controllers/actions/planning folder like this:

    namespace app\modules\dashboard\controllers\actions\planning;
    
    use yii\base\Action;
    use yii\web\Response;
    
    class PurchaseCreateAction extends Action
    {
        // string because we will return rendered form view and
        // Response because we will return redirect after successful create
        public function run(): string|Response
        {
             // ... action logic
    
             // to redirect
             return $this->controller->redirect(...);
    
             // to render view
             return $this->controller->render(...);
        }
    }
    

    You can include the standalone action in your planning controller like this:

    namespace app\modules\dashboard\controllers;
    
    use app\modules\dashboard\controllers\actions\planning\PurchaseCreateAction;
    use yii\web\Controller;
    
    class PlanningController extends Controller
    {
        public function actions()
        {
            return [
                'purchase-create' => PurchaseCreateAction::class,
                // ... other actions
            ];
        }
    }