HMVC pattern

I would like to start discussion about HMVC pattern implementation possibility.

maybe this way? http://paste.laravel.com/4MF

What's the reason for HMVC? Just checking, because the concept of "reusable sub-controllers" can be implemented via models in middleware tier.

Roman what do you mean? Any success and any example? I just though about it and maybe it is better to stick with that phalcon offers. In the end there are classes such as \Phalcon\Mvc\User\Plugin, \Phalcon\Mvc\User\Module, \Phalcon\Mvc\User\Component I think that it will be the base for future extensions. After some time I did everything in Controller using plugin Class, so in View there are results what is done in Controller.

But please write about your solution, maybe it is very good idea worth to go?

I literally meant it asking what the reason was to have HMVC in Phalcon :)

There is a single Model-View-Controller triad in typical MVC application which does all the work.

HMVC being an extension to MVC has one MVC triad at the top of the pyramid (if we imaging it as a pyramid). This triad handles main request. There are also several triads (sub-controllers in the current flow) underneath it. Main controller is able to execute sub-request to any of them and then it deals with data they return, if any. Sub-controller can be public or private: doesn't have index action or controller actions cannot be accessed in the main request. All public sub-controllers' actions CAN BE accessed in the contrast. So you can have a same controller processing requests from a visitor (as standalone component) and sub-requests from other controllers.

It adds great flexibility, but it should be used wisely. HMVC requires good application architecture otherwise it becomes overcomplicated, leads to unexpected failures etc. There are some architectural and usability problems in HMVC: e.g., calling protected sub-controller from public controller, handling sub-controller failures from the main controller, creating sub-controllers and delegating them some responsibilities in a proper way; complex core and requests system, need to handle main and sub-requests separately and so on. Beside this, at the end HMVC does exactly the same things as typical MVC. It just has slightly more complicated structure and different code organization.

In MVC all logic goes into a model. Such models all together are called middleware. They connect controller and any underlying libraries, all they simply contain some logic.

Just to notice: MVC model IS NOT Phalcon\Mvc\Model. Sounds like a paradox, but the latter should have been called Phalcon\Orm\Model or similar. To give you a clue, imaging a service which is built on MVC and performs data encryption. Service does not use a database. So what is a Model in this case? It's the entity which actually contains the logic to encrypt/decrypt data.



85.0k

I added an initial HMVC bootstrap to the MVC examples: https://github.com/phalcon/mvc/tree/master/hmvc

Its bootstrap registers itself as a service, the application provides a method request that allows to make HVMC requests:

<?php

use Phalcon\Mvc\Controller;

class IndexController extends Controller
{

    public function indexAction()
    {
        var_dump($this->app->request(array(
            'controller' => 'say',
            'action' => 'hello'
        )));
    }

}

This calls the say/hello action inside index/index, SayController::helloAction looks like this:

<?php

use Phalcon\Mvc\Controller,
    Phalcon\Http\Response;

class SayController extends Controller
{

    public function helloAction()
    {
        return new Response('hello there');
    }

}

It simply returns a new response that is printed in the other side of the request IndexController::indexAction, I'm pretty sure it needs some more work to support the full HTTP foundation, views, etc.

The HVMC request method is this: https://github.com/phalcon/mvc/blob/master/hmvc/public/index.php#L49

Sorry for quick response, i am in a hurry but want to say my excitement about this post.

It is very exciting. Thank you. Now we have possibility to write and test tiny parts of application using url without write helper classes :) I think that it is first step to plugin database,

for example in router : ```php $router->add('/mySliders', array( 'controller' => 'slider', 'action' => 'version1' )); ``` and we can test http://example.com/mySliders in browser what is happening in output

after test we can use it In application like this

use Phalcon\Mvc\Controller;

class IndexController extends Controller
{

  public function indexAction()
  {

        $this->view->setVar('slider',
            $this->app->request(
                'controller' => 'slider',
                'action' => 'version1',
                'flag' => 'embeded' // to display without html wraping
            )
        );
  }

}

How this solution impact on performance of framework?

I am about to finishing first project in phalcon not using hmvc, but next project i would like to use it in lots of places.



85.0k

I think this implementation is very fast, since it doesn't implement the routing and view layers, by now, it's only capable of produce raw responses, so it's optimized to return json, xml, or one level of html, but it could be easily improved to support more things.

I hope it's not so old. Does this request brings all the say/hello.volt content? Is there a way to get it?



65
edited Jun '14

Hi Phalcon,

I got a problem rendering nested view, here is what I'm trying to do I changed your 'request' function into an Elements module

namespace Modules\Main\Libraries;

/**
 * Elements
 *
 * Helps to build UI elements for the application
 */
class Elements extends \Phalcon\Mvc\User\Component
{

    public function loadModule($path = '', $data = array()) {
        $di = clone $this->getDI();
        $dispatcher = $di->get('dispatcher');

        $paths = explode('/', $path);
        $data = is_array($data) ? $data : array($data);

        // get controller name
        if (isset($paths[0])) {
            $controller = $paths[0];
        }

        // get action name
        if (isset($paths[1])) {
            $action = $paths[1];
        }

        // get params
        if (isset($paths[2])) {
            array_splice($paths, 0, 2);
            $params = array_merge($paths, $data);
        } else {
            $params = $data;
        }

        if (!empty($controller)) {
            $dispatcher->setControllerName($controller);
        } else {
            $dispatcher->setControllerName('index');
        }

        if (!empty($action)) {
            $dispatcher->setActionName($action);
        } else {
            $dispatcher->setActionName('index');
        }

        if (!empty($params)) {
            if(is_array($params)) {
                $dispatcher->setParams($params);
            } else {
                $dispatcher->setParams((array) $params);
            }
        } else {
            $dispatcher->setParams(array());
        }

        $dispatcher->dispatch();

        $response = $dispatcher->getReturnedValue();
        if ($response instanceof ResponseInterface) {
            return $response->getContent();
        }

        return $response;
    }
}

and I have 2 controllers:

namespace Modules\Main\Controllers;

class IndexController extends ControllerBase
{

    public function indexAction()
    {
        $secondContent = $this->elements->loadModule('test/hello/json');

        $this->view->setVar('secondContent', $secondContent);
    }

}

and

namespace Modules\Main\Controllers;

use \Phalcon\Http\Response;

class TestController extends ControllerBase
{

    public function indexAction()
    {

    }

    public function helloAction($format='html', $param = 'empty')
    {
        $this->view->setVar('content', 'Hello this is test value "'.$param.'"');

        $content = $this->view->getContent();

        return (string)$content;

        // return 'Hello this is test value "'.$param.'"';
    }

}

my DI

        $di['elements'] = function() {
            return new \Modules\Main\Libraries\Elements();
        };

Views files IndexController::Index

<h1>Congratulations!</h1>

<p>You're now flying with Phalcon. Great things are about to happen!</p>
<p>Second content: {{ secondContent}}</p>
<p>HMVC: {{ elements.loadModule('test/hello/json', 'test') }}</p>

and HelloController::test

This is :: {{ content }}

expecting to get https://docs.google.com/a/heldes.com/file/d/0B2CEqyPg0RjXNkZaU3hNcVJGWDQ/edit?usp=drivesdk

but it only rendering the HelloController: https://docs.google.com/a/heldes.com/file/d/0B2CEqyPg0RjXbFI5ZFROQk5Ocm8/edit?usp=drivesdk

if I change IndexController::indexAction to

    public function indexAction()
    {
        $secondContent = '';

        $this->view->setVar('secondContent', $secondContent);
    }

and TestController::helloAction to

    public function helloAction($format='html', $param = 'empty')
    {
        $this->view->setVar('content', 'Hello this is test value "'.$param.'"');

        $content = $this->view->getContent();

        //return (string) $content;

        return 'Hello this is test value "'.$param.'"';
    }

the result that i get is: https://docs.google.com/a/heldes.com/file/d/0B2CEqyPg0RjXeDgzSzhXeTdxLW8/edit?usp=drivesdk

Any solution to solve this ?

Thanks, Helman

edited Dec '15

Introcing a Widget object, that mimics the behaviour of the Phalcon/Mvc/Application class can be a solution.

Such a class good look like:

    class Widget extends Plugin{

        public function handle($uri, $viewLevel = View::LEVEL_ACTION_VIEW) {
            // create a new router, top not mess up the actual route being processed, for which this
            // widget is called.
            $router = new \Phalcon\Mvc\Router();
            $router->handle($uri);

            // clone the dispatcher, to no mess up processing of the actual route being processed.

            $dispatcher = clone $this->di->getDispatcher();       
            $dispatcher->setModuleName($router->getModuleName());
            $dispatcher->setNamespaceName($router->getNamespaceName());
            $dispatcher->setControllerName($router->getControllerName());
            $dispatcher->setActionName($router->getActionName());
            $dispatcher->setParams($router->getParams());

            $view = $this->di->getView();

            //Set the render view level, normally View::LEVEL_ACTION_VIEW but you can opt for other solution.
            $view->setRenderLevel($viewLevel);
            $view->start();

            $dispatcher->dispatch();

            $view->render($dispatcher->getControllerName(),
                      $dispatcher->getActionName(),
                      $dispatcher->getParams()
            );

            // return the content
            return $view->getContent();
        }    
    }

Your index controller looks like:

    class IndexController extends ControllerBase
    {

        public function indexAction()
        {
            \\ Create the widget..
            $widget = new Widget($this->di);

            \\ Have it process the route Test/index returnin anly the action level view.
            $response = $widget->handle('/Test/index');

            \\ Set the returned contet to be included in the view of this route.
            $this->view->setVar('Sub', $response);   
        }
    }

The TestContoller looks like:

    class TestController extends ControllerBase {

        public function indexAction()
        {
            $this->view->setVar('test', 'Developper');
        }
    }

The views could be: Index/Index:

    <div class="page-header">
        <h1>Congratulations!</h1>
    </div>

    <p>You're now flying with Phalcon. Great things are about to happen!</p>

    <em>This page is located at views/index/index.phtml</em>

    <p> {{ Sub }} </p>

Test/index:

    <div class="page-header">
        <h1>Congratulations!</h1>


        <p>Inserted by requesting a Controller, {{ test }}</p>

    </div>

Alternatively one could do in the index/index controller:

    class IndexController extends ControllerBase
    {

        public function indexAction()
        {
            \\ Create the widget..
            $widget = new Widget($this->di);

            \\ Set the returned contet to be included in the view of this route.
            $this->view->setVar('Sub', $widget);   
        }
    }

and then call it from the view:

Index/Index:

    <div class="page-header">
        <h1>Congratulations!</h1>
    </div>

    <p>You're now flying with Phalcon. Great things are about to happen!</p>

    <em>This page is located at views/index/index.phtml</em>

    <p> {{ Sub.handle('\test\index') }} </p>

Note that this implementation ingnores any pre an post processing events that Phalcon\Mvc\Applciation would call. And cross module calls might not always work. Ideally one should implement Widget in Zephir as a copy of Mvc/Appplication, that uses a copy of the Router and Dispatcher objects on the DI, and limits the view to how it is called. But I can't get Zephir to clone objects...

IMHO

I used HMVC in Phalcon and it was not bad but no more. Now i prefer reusable View. According Padraic Brady "Fat Stupid Ugly Controllers" - View know about Model and can make query - no plain SQL in View! all SQL only in Model as static function, so:

  1. i can reuse VIew
  2. i have simple but powerful View cache
  3. for something else i create Widget (it's just for minimize php in View,) but call him in View. Widget i create only for minimize code in view. When php code in View more 50% - time to create Widget :)
  4. Controller only for control - for example validation user data or switch View - (like themes or view mode etc)
  5. it's fast

It is true that a lot can be accomplished with reusable view(s)(parts), accessing static model methods directly.

But there are also many use cases in which using HMVC is preferable. For example when you want to enforcing access in a unified way with ACL, allowing front-end designers or users to determine what is shown on a page-type, etc.

IMHO a framework should offer both methods. Reusing views with partials and view inheritance etc has good coverage in Phalcon.

Let this discussion be the start of a proper implementation of the ability to execute controllers (and their related views) from a main request - controller or view.

edited Sep '17

Hello All! I know this is a post about HMVC. But, I just want to know if you have an updated tutorial or example code (though a tutorial will be very much appreciated) on how to use HMVC in phalcon. Thank you.