I would like to start discussion about HMVC pattern implementation possibility.
maybe this way? https://paste.laravel.com/4MF
|
Jun '17 |
10 |
5336 |
9 |
I would like to start discussion about HMVC pattern implementation possibility.
maybe this way? https://paste.laravel.com/4MF
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.
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 :
$router->add('/mySliders', array(
'controller' => 'slider',
'action' => 'version1'
));
and we can test https://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.
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
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:
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.