CRUD controllers
Moderator: General Moderators
- allspiritseve
- DevNet Resident
- Posts: 1174
- Joined: Thu Mar 06, 2008 8:23 am
- Location: Ann Arbor, MI (USA)
CRUD controllers
For a long time I've been using the same template for adding content as for editing content. However, I've always been using separate controllers. Seems to me that's leading to a lot of duplication. So I'm wondering: do you use separate controllers (or methods) for adding/editing, or do you have conditionals to determine whether to insert or update?
Re: CRUD controllers
I use the same controller - separate actions
- allspiritseve
- DevNet Resident
- Posts: 1174
- Joined: Thu Mar 06, 2008 8:23 am
- Location: Ann Arbor, MI (USA)
Re: CRUD controllers
OK, so essentially a vote for keeping them separate. I feel like there's generally more shared code than distinct code between the two, but maybe that's acceptable for the freedom it gives when they do deviate.pytrin wrote:I use the same controller - separate actions
Re: CRUD controllers
yes, I agree - similar, but distinct enough to keep separate.
- Christopher
- Site Administrator
- Posts: 13596
- Joined: Wed Aug 25, 2004 7:54 pm
- Location: New York, NY, US
Re: CRUD controllers
Same controller, same actions -- the Model deals with whether there is a primary key value (update) or not (insert).
(#10850)
Re: CRUD controllers
My CRUD controller has evolved over time
My index action instantiates my "grid" framework, which calls a getModels() template method and calls addModels() on the grid, the grid uses decorators so I just put echo $this->grid in my templates.
My edit action first calls the "findOrCreateModel" template method, then the getForm TM, then my forms all know how to populate from / to a model. then i ask the form if it is valid, lastly I locate the mapper for the current model and save it if so. I have template methods for things like generating "successful save" messages, and all sorts of other template methods wherever behavior needs to vary.
I started by creating duplication but refactoring and refactoring until my "concrete" controllers turned out to be just a few lines or so long. Most of my abstract classes now adays provide a default implementation of the template method, reading its configuration from object properties, this allows me to pick and choose granular configuration to override, override a whole template method with alternative implementation, etc..
My index action instantiates my "grid" framework, which calls a getModels() template method and calls addModels() on the grid, the grid uses decorators so I just put echo $this->grid in my templates.
My edit action first calls the "findOrCreateModel" template method, then the getForm TM, then my forms all know how to populate from / to a model. then i ask the form if it is valid, lastly I locate the mapper for the current model and save it if so. I have template methods for things like generating "successful save" messages, and all sorts of other template methods wherever behavior needs to vary.
I started by creating duplication but refactoring and refactoring until my "concrete" controllers turned out to be just a few lines or so long. Most of my abstract classes now adays provide a default implementation of the template method, reading its configuration from object properties, this allows me to pick and choose granular configuration to override, override a whole template method with alternative implementation, etc..
- allspiritseve
- DevNet Resident
- Posts: 1174
- Joined: Thu Mar 06, 2008 8:23 am
- Location: Ann Arbor, MI (USA)
Re: CRUD controllers
I would be interested in seeing an example of this at work, if you have time and don't mind.josh wrote:I started by creating duplication but refactoring and refactoring until my "concrete" controllers turned out to be just a few lines or so long. Most of my abstract classes now adays provide a default implementation of the template method, reading its configuration from object properties, this allows me to pick and choose granular configuration to override, override a whole template method with alternative implementation, etc..
Re: CRUD controllers
I can show a few parts I suppose.
The save TM does some "top secret" stuff, and then calls this method
before calling ->getMapper() and then calling ->save() on that with the "populated" model. The forms automgically know how to populate to models as well but can be overriden if needed via a TM in the form class. The save Action also calls this TM
This allows me to hard code a message on the controller property or to override the whole method if my message is not a literal
There is more but I hope you get the idea. In hindsight the only thing I would change is write unit tests for my controllers but I didnt have the right combination of experience + time to do that on this project, I unit test all the models tho ( or try to )
Code: Select all
public function editAction()
{
$model = $this->findOrCreateModel();
if( !$model )
{
return;
}
$form = $this->getForm();
$form->populateFrom( $model );
if( $this->getRequest()->isPost() && $form->isValid( $this->getRequest()->getPost() ) )
{
$this->save( $form, $model );
}
$this->view->form = $form;
$this->view->id = $model->getId();
$this->view->model = $model;
}Code: Select all
protected function populateTo( K12_Form $form, Shuffler_Model $model )
{
$form->populateTo( $model );
}Code: Select all
/**
* Perform the post edit action, queues a flash message
* and returns to the index listing
*/
protected function doEdit( $id )
{
if( $id )
{
$this->flashMessenger->addMessage( $this->saveMessage );
}
else
{
$this->flashMessenger->addMessage( $this->newMessage );
}
return $this->_redirect( $this->getIndexUrl() );
}There is more but I hope you get the idea. In hindsight the only thing I would change is write unit tests for my controllers but I didnt have the right combination of experience + time to do that on this project, I unit test all the models tho ( or try to )
Re: CRUD controllers
I used to do something similar, but the interface became too abstract and magical for my taste. Many specific instances also rendered it unapplicable. In the end, I went back to manually writing those methods and I found it much more flexible.
- Christopher
- Site Administrator
- Posts: 13596
- Joined: Wed Aug 25, 2004 7:54 pm
- Location: New York, NY, US
Re: CRUD controllers
For comparison, here is some code copied out of a Skeleton example. Forms are considered temporary Models, so filtering and validation can be shared.josh wrote:I can show a few parts I suppose.
Code: Select all
public function editAction()
{
$usersmodel = $this->_load()->model('Users');
$view = $this->_load()->view('Form');
// Instantiate a new form model/controller
$form = new A_Model_Form();
// Hand the Form the fields and rules from the model
$form->addRule($usersmodel->getRules());
$form->addField($usersmodel->getFields());
// Now add an additional field, the second password field. Which must match the first password field.
// The $form get the Rules for the first password field from $usersmodel
$form->addField($passwordfield = new A_Model_Form_Field('password2'));
// now we add an additional rule, specific for the form we are dealing with.
$form->addRule(new A_Rule_Match('password', 'password2', 'Password 2 must match Password 1'));
// ask the form if it is valid. The form checks internally if the model fields are valid?
if($form->isValid($this->request)){
// save
$usersmodel->save($form->getSaveValues());
// redirect to user detail page or whatever
} else {
// show errors if submitted
$view->setErrorMsg($form->getErrorMsg());
}
$view->setValues($form->getValues());
// show form here
}(#10850)
Re: CRUD controllers
What do you do if there isn't a primary key? In a one-to-many table for example...arborint wrote:Same controller, same actions -- the Model deals with whether there is a primary key value (update) or not (insert).
Re: CRUD controllers
Probably the Model is not in 1:1 mapping with the tables, and the one-to-many is part of the model, at least I would design it in this wayonion2k wrote:What do you do if there isn't a primary key? In a one-to-many table for example...arborint wrote:Same controller, same actions -- the Model deals with whether there is a primary key value (update) or not (insert).
P.S. I also prefer the same controller - same action - model decides if it should update or insert approach.
Re: CRUD controllers
Could always have a composite key.. if on a link table, or one to many, the key would be on both id's.onion2k wrote:What do you do if there isn't a primary key? In a one-to-many table for example...arborint wrote:Same controller, same actions -- the Model deals with whether there is a primary key value (update) or not (insert).
- Christopher
- Site Administrator
- Posts: 13596
- Joined: Wed Aug 25, 2004 7:54 pm
- Location: New York, NY, US
Re: CRUD controllers
Well, first of all we are talking about CRUD, so having a one-to-many relationship and having the user deal with it all on one screen is not that common. Often you would have child CRUD screens to deal with editing dependent records. I have built CRUD editing pages that pulled in multiple records from multiple tables and allowed the user to edit them all on the same page.onion2k wrote:What do you do if there isn't a primary key? In a one-to-many table for example...
My solution has been to make the Model do the hard work. From the CRUD point-of-view the Model makes the data appear as a single record. The complexity of relationships (load and save) is dealt with inside the Model. The "datasource" Model provides the temporary "form" Model with data, filters and rules. The form Model deals with that data until all rules pass, then give it back to the datasource Model to sort out.
(#10850)
Re: CRUD controllers
You have to let them differ and fight the "not caring", you have to refactor and identify the parts that differ and extract them to template methods. If your controllers don't share behavior you are probably writing FAT controllers or your application is not CRUD based.pytrin wrote:I used to do something similar, but the interface became too abstract and magical for my taste. Many specific instances also rendered it unapplicable. In the end, I went back to manually writing those methods and I found it much more flexible.
For instance I had like 10 modules that implemented their own edit action, posting a job and reposting a job are 2 different hybrids of the "create" action, originally I had different editActions() on every controller, then later on I was able to refactor it so I have one overall editAction(), one place to change how my form validation causes my application to react, if I want to change how a model is created I can override findOrCreateModel(). If I need to hook save behavior I can do it in the controller or the mapper's hook method, If I want to pull a custom result set I override getModels(), If I need customized grid listings I subclass the grid object and / or override getGrid() TM. Its all about finding the right TMs to make
For editing dependent records I usually build it into my edit screen's hook methods, I'll build some "child grids" and render them with the parent record, which generates a screen where the user can see ( lets say order line items as well as the master order ), and can click on the line items. My grid uses elements like zend form so the grid or controller itself is ignorant as to what the links are, or what content it is handling.
It has worked out very well this way. If something is different I can override an action or the whole controller, abstraction is not the root of all evil despite what some people think. If there is no place to override something I stop and think about which TM I am missing, not about how abstraction is somehow the root of all evil
I eliminated over 700 lines of executing controller code ( which is 1/10th of my LOC vanished without changing behavior at all ) in 4 days of refactoring after I took up this architecture. Sometimes you just get used to leaving with the bloat, doesnt make it the best design.
If you are already "over the edge", try to create little utility helper methods and removing repetition, over time as your code becomes more concise it will be more apparent what to do next. For instance I _always_ ask a form if it is valid before rendering it, that is a "utility method" ( template method ) right there.
Its unrealistic to fit a greenfield application into some cookie cutter architecture tho, start by writing your editAction() procedurally, then refactor out repitition, if you have unit tests on your controllers itll go 1000x easier I promise