MVC & Pagination, Filtering, Ordering, etc.

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

MVC & Pagination, Filtering, Ordering, etc.

Post by VladSun »

(*assuming that I do correct assumptions about MVC pattern, *correct* me if not)
Pagination, Filtering, Ordering, etc. - where these are to be implemented in an MVC powered architecture?!?

Let's take the most common case - Pagination.

So ... without a Pagination we have:
- Model - executes SQL "selectAll" statement into a record collection;
- View - shows collection of records;
- Controller - does almost nothing;

With a Pagination we have (?!?):
- Model - executes SQL "selectAll" statement with a LIMIT clause into a record collection;
- View - shows collection of records;
- Controller - does almost nothing;

OK ... but it's obvious (at least to me) that Pagination should not be implemented into the Model because it's naturally a View requirement (view only N-to-M records). Additionally, in my Views I have a so called "view-context" - that is if I call "/users/get.html" it will be outputed as an HTML content , if I call "/users/get.csv" it will be outputed in a CSV format. And I don't need *any* Pagination applied when outputting as CSV, while I need Pagination applied when ouputting as HTML. The view-context output is controlled by the Front controller, so it has nothing to do with the Model.

Can anybody clear this for me?

What I do for now is to have an "Interceptor" object which behaves as a "plugin" for both Model and View objects. This "plugin" is inserted by the Controller.
Last edited by VladSun on Sun Dec 06, 2009 5:47 pm, edited 2 times in total.
There are 10 types of people in this world, those who understand binary and those who don't
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: MVC & Pagination, Filtering, Ordering, etc.

Post by josh »

Concerns span multiple layers, indeed cross cutting concerns.

The controller would be able to take an input from the user that specified how he wanted his results presented (ordering, filtering). So basically the controller provides an interface in the form of GET parameters that can be passed. From there it tells the business logic to do it's work (wether that reside in a model or controller).

For pagination, theres more calculations to do like dividing out the page numbers by the rows per page, multiplying by the current page and subtracting the rows per page. For this there are frameworks (eg. Zend_pagination) which do nothing but the maths for you (if you wish).

Then of course you have the view, for which Zend_Pagination has a decorator system and several display styles. With your sorting you will probably also have some more code in the view (unless you expected your users to use the GET parameters).

As far as the .csv, the way I saw the Magento guys handle this (not that theyre a good example of well done MVC or anything), they just made another action, and simply chained a call that reset the page count, and rendered the csv's view. I wouldn't be too dogmatic about re-using actions, if taken to the extreme you would just have 1 god action. If creating 2 actions bothers you (duplication), extract a private parametrized method, and call that method from each action.

So my overall view on this is that context switching is a useful tool, but not a means to an end in itself. The solution with 1 single action might just take you longer and not read as good
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: MVC & Pagination, Filtering, Ordering, etc.

Post by VladSun »

Hm ... I wonder whether my design is right then...

Code: Select all

abstract class CRUD_Controller extends Auth_Controller
{
    protected $_model       = null;
...
    protected function setModel($model)
    {
        if (!empty($model))
        {
            $this->_model = $model;
        }
    }
 
    public function get()
    {
        $this->getModel()->request('getAll');
 
        foreach ($this->getModel()->get() as $key => $value)
            RowResponse::register($key, $value);
 
        $this->view('response');
    }
Depending on the view context (i.e. /address/get.html ) $this->view('response') may render "json/response", or ""html/response", etc.

Code: Select all

class Address extends CRUD_Controller
{
    function __construct()
    {
        parent::__construct();
 
        $this->setModel(new Pagination(new Address_CRUD_Model()));
    }
}
So, Pagination "decorates" the CRUD_Model by modifying its SQL query before it gets executes, thus adding pagination functionality to the Model. The Model doesn't know it has been "paginated".
But here is the problem - I need Pagination for the HTML view, but I don't need it for the CSV view and my Controller doesn't know how many type of view there are and which types require Pagination and which not.
My Views (probably that's my mistake) works with the Model data through a Response object - an object that always has a "success" property, an "errorMessages" property and a "data" property.

By using this:

Code: Select all

foreach ($this->getModel()->get() as $key => $value)
RowResponse::register($key, $value);
I aim to modify the data properties of the Response generated by the Model. Every "decorator" may change the data ouputed by the previous "decorator" or Model. This way I successfully added the "totalCount" property to the Response object generated by the Pagination decorator, so it's accessible by the View.

Thoughts? Criticism?

PS: I always use POST method for input data (i.e. I DON'T use URL schemes like this: /user/get/14)
There are 10 types of people in this world, those who understand binary and those who don't
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: MVC & Pagination, Filtering, Ordering, etc.

Post by josh »

The way I do it I made a Grid library. (i cant say that any approach is "wrong" per se)
I assign a collection (I guess another name for "crud model, but more clearly shows that it is in fact a collection of models). So my grid interface has something like addModels() that is abstract. I make a grid subclass for each module which specifies/overrides fields (grids can inherit and remove eachother's fields just like Zend_Form)

So I might have

A_Model_Grid // has fields only the owner of the records cares about
A_Model_Grid_Search // has fields users that are searching shold see
A_Model_Grid_Admin // has more fields only the admin should see
etc...

Usually addModels() is implemented only once and adds all fields from the model, if the grid doesn't have a field key registered it silently ignores it So we dont have to duplicate addModels() if we are duplicating grids. Only specify what to add/remove in terms of display fields (columns)

In my crud control I have inherited methods that will return an already paginated collection, and all the integers I need to calculate total pages, etc... I simply tell the grid these numbers (which it tells to it's pagination adapter, currently Zend_Paginator)

Then in the view I simply have

echo $this->grid


Each grid has decorators, 99% of the time the default decorators are used. Each grid also has the concept of filters, which do nothing but send GET values back to the controller. The controller uses the mapper to do the filtering, the grid only depends on collection classes, not on mappers. So for instance if I am making a controller for A_Model but I only want to list where the owner_id = the current user

I override a template method

protected function doGetModels();

or better yet

protected function doFiltering()

In there I simply chain filters that filter models before the grid gets them. I wrote more detail about my controllers here and some code samples I think
viewtopic.php?f=19&t=104244&hilit=+grid
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: MVC & Pagination, Filtering, Ordering, etc.

Post by Christopher »

VladSun wrote:I aim to modify the data properties of the Response generated by the Model. Every "decorator" may change the data ouputed by the previous "decorator" or Model. This way I successfully added the "totalCount" property to the Response object generated by the Pagination decorator, so it's accessible by the View.
I am a little confused about what question you are asking exactly? Essentially, the View is a "decorator" for the Model. Views take data from the Model and format it as specified by the Request (i.e. Controller) and generate a Response.

You question is in the murky View/Controller boundary. Where you put that separation is less important that whether you and your application are happy about where you put that separation. So my question is: What is not working for you?
(#10850)
Cirdan
Forum Contributor
Posts: 144
Joined: Sat Nov 01, 2008 3:20 pm

Re: MVC & Pagination, Filtering, Ordering, etc.

Post by Cirdan »

In my project, I don't put the paginator as apart of the MVC model. I imitated Zend in the way that the paginator is a view helper [$this->paginate($numberOfPages,$currentPage)]. So it's neither a view nor a controller, but rather a separate chunk of code that can be moved around. However there is a flaw with this design. What if the designer wants to change the pagination style? They would have to get into the system code instead of being able to just edit a view script.
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: MVC & Pagination, Filtering, Ordering, etc.

Post by VladSun »

arborint wrote:Essentially, the View is a "decorator" for the Model. Views take data from the Model and format it as specified by the Request (i.e. Controller) and generate a Response.
But the View doesn't change the way a Mode behaves - it only shows its data in some way. While my "decorator" objects change the data properties of the Model thus creating a Model from a Model.
arborint wrote:So my question is: What is not working for you?
For example - multiple mime-type Views. I think It should be handled in the Front controller or in the base Controller class (the way I do it):

Code: Select all

abstract class View_Context_Controller extends Controller
{
    protected   $requestedViewMode          = null;
    protected   $defaultViewMode            = 'json';
    
    public function __construct()
    {
        parent::__construct();
    }
 
    protected function setDefaultViewMode($viewMode)
    {
        $this->defaultViewMode = $viewMode;
    }
 
    protected function getViewMode($viewMode = null)
    {
        if ($viewMode)
            return $viewMode;
 
        if ($this->requestedViewMode)
            return $this->requestedViewMode;
 
        if ($this->defaultViewMode)
            return $this->defaultViewMode;
 
        return 'html';
    }
    
    public function view($view, $data = null, $viewMode = null)
    {
        $this->load->view($this->getViewMode($viewMode).'/'.$view, $data);
    }
 
    protected function getRemapping($method, $arguments)
    {
        $mv = explode('.', $method);
 
        if (isset($mv[1]))
            $this->requestedViewMode = $mv[1];
 
        $o = new stdClass();
        $o->method      = $mv[0];
        $o->arguments   = $arguments;
 
        return $o;
    }
}
And in my Controllers I don't need to handle the mime-type requested - I just call $this->view('response') and a view according to the current View context is loaded. But as I've mentioned above - not all of my mime-type views need pagination for example.
There are 10 types of people in this world, those who understand binary and those who don't
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: MVC & Pagination, Filtering, Ordering, etc.

Post by Christopher »

Cirdan wrote:the paginator is a view helper
I think there are three major parts to any pagination scheme, even it if the code is monolithic. It needs to be able to:

1. get Request values to determine the current page, and do any calculations for start/end/# records, bounds checks, etc.,

2. fetch only the records to be displayed,

3. display the fetched records and links to support paging through all the records.

Typically, the code for #1 is in the Controller, but calculation code can be in the Model and/or View. #2 in the Model and #3 in the View.
(#10850)
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: MVC & Pagination, Filtering, Ordering, etc.

Post by Christopher »

VladSun wrote:But the View doesn't change the way a Mode behaves - it only shows its data in some way. While my "decorator" objects change the data properties of the Model thus creating a Model from a Model.
I don't see any reason why your View can't tell the Model what "context" it wants the data in. It can call the Model directly with any information needed.
VladSun wrote:For example - multiple mime-type Views. I think It should be handled in the Front controller or in the base Controller class (the way I do it):

And in my Controllers I don't need to handle the mime-type requested - I just call $this->view('response') and a view according to the current View context is loaded. But as I've mentioned above - not all of my mime-type views need pagination for example.
It looks like you have View code leaking into your Controller. If you are going to leak, I think it is better to push code out of the controller into the Model and View.
(#10850)
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: MVC & Pagination, Filtering, Ordering, etc.

Post by VladSun »

arborint wrote:I don't see any reason why your View can't tell the Model what "context" it wants the data in. It can call the Model directly with any information needed.
I am trying to have a clear business logic into the Model - I mean really "business" without a single interference to the View requirements(I believe that (e.g.) Pagination related stuff has nothing to do with "business logic").

Now I think, that my main problem is that I use intermediate communication layer between the View and the Model - my Response object.
Also my model methods are executed in the Controller and probably if I move their execution into the View (which one is loaded depends on the view-context) I can control the Model behavior by adding "decorators". I'll try this.
arborint wrote:It looks like you have View code leaking into your Controller. If you are going to leak, I think it is better to push code out of the controller into the Model and View.
hm... you mean like this (suppose we have fetched somehow the $context value):

Code: Select all

class My_Controller
{
    function get()
    {
        $this->load->model('MyModel');
        $this->load->view('myview', $this->model, $this->viewContext);
    }
}
- how it's better than

Code: Select all

class My_Controller extends View_Context_Controller
{
    function get()
    {
        $this->load->model('MyModel');
        $this->view('myview', $this->model);
    }
}
?
Last edited by VladSun on Tue Dec 08, 2009 3:09 am, edited 1 time in total.
There are 10 types of people in this world, those who understand binary and those who don't
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: MVC & Pagination, Filtering, Ordering, etc.

Post by VladSun »

Maybe I'm so confused about these things, because I've used ExtJS for a long time and things like Pagination, sorting filtering may be done locally - after all data is fetched and stored client side. So, I am used to have very "clean" Models.

PS:
I am trying to have a clear business logic into the Model - I mean really "business" without a single interference to the View requirements(I believe that (e.g.) Pagination related stuff has nothing to do with "business logic").
For example:
- get all members = "clear business" logic
- get members that belong to a given group = "clear business" logic
- get members that have names starting with "Vla" != "clear business" logic
- get first 30 members != "clear business" logic
There are 10 types of people in this world, those who understand binary and those who don't
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: MVC & Pagination, Filtering, Ordering, etc.

Post by Christopher »

VladSun wrote:hm... you mean like this (suppose we have fetched somehow the $context value):
- how it's better than ?
Well, it is simpler. It gets rid of all the ViewMode stuff in the Controller. And it makes the contract direct on the Model from the View.
(#10850)
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: MVC & Pagination, Filtering, Ordering, etc.

Post by Christopher »

VladSun wrote:Maybe I'm so confused about these things, because I've used ExtJS for a long time and things like Pagination, sorting filtering may be done locally - after all data is fetched and stored client side. So, I am used to have very "clean" Models.

For example:
- get all members = "clear business" logic
- get members that belong to a given group = "clear business" logic
- get members that have names starting with "Vla" != "clear business" logic
- get first 30 members != "clear business" logic
I think my "clear" you mean "simple." If you added a getCountOfMembers() and getLimitMembers($start, $size) your Model would be no less "clear" and would allow the View to deal with Pagination pretty easily.
(#10850)
josh
DevNet Master
Posts: 4872
Joined: Wed Feb 11, 2004 3:23 pm
Location: Palm beach, Florida

Re: MVC & Pagination, Filtering, Ordering, etc.

Post by josh »

VladSun wrote: For example:
- get all members = "clear business" logic
- get members that belong to a given group = "clear business" logic
- get members that have names starting with "Vla" != "clear business" logic
- get first 30 members != "clear business" logic
I would not consider any of that to be a part of the business tier, I would consider that a persistence concern. It sounds like you are asking an awful lot of overlapping questions at once though so I'm not sure what the issue is.

Can you try to sum up in simple english what you're trying to do (without using words like decorator, model, and context?).

In my application collections are just collections, models are just models. and then I have "finders" (data mappers). Wether the data mapper I am using is going to return only a 20 item collection, or all 2,000 items, I need not know (its simply an implementation detail)

$collection = $this->paginator_mapper->findAll( 2, 20 ) ; // might have 20 items
$collection = $this->plain_old_vanilla_mapper->findAll() ; // might have 2,000+ items

There's no need to over complicate it more then that, in my opinion (many people feel its necessary to do lazy loading in the collection, that I have never had a need for)
User avatar
VladSun
DevNet Master
Posts: 4313
Joined: Wed Jun 27, 2007 9:44 am
Location: Sofia, Bulgaria

Re: MVC & Pagination, Filtering, Ordering, etc.

Post by VladSun »

OK, I'll try...

1) Imagine a building full of people. If you want to visit them you just go to that building and there they are.
2) Imagine a building full of people. There are some rooms in it. If you want to visit people in a room you just go to that building, then go to the room and there they are.
3) Imagine a building full of people. There are some levels and some rooms on every level. If you want to visit people in a room you just go to that building, go to the level, then go to the room and there they are.

I think this story can be mapped into MVC as:
1) "building", "level", "room" and "people" are business domain objects. They all exist in the real world. It's all very natural.
2) "visiting" is a "natural" finder method.
3) finally, you will meet them face to face - thats for the "View".

Now ...
You want to visit people having names starting with 'V' ... Apparently, you can't just visit a place (it doesn't exist) where there is a bunch of people with names starting with 'V'.

So, what I'm trying to do is to build another building with rooms for every group of people whose names start with a particular letter. I have to move the people to that building and then I can visit the people in a room and meet them face to face.

I think that while the first building is a "natural" one, the second building is a "surrogate" one. Also, you can't have (in real life) a building that have both types of rooms, because you can't have the people be there at the same time. While I can build many types of "surrogate" buildings I can have a single "natural" building build.

:)
There are 10 types of people in this world, those who understand binary and those who don't
Post Reply