Monday, December 22. 2008Using Zend_Form in Your ModelsA number of blog posts have sprung up lately in the Zend Framework community discussing the Model in the Model-View-Controller pattern. Zend Framework has never had a concrete Model class or interface; our stand has been that models are specific to the application, and only the developer can really know what would best suit it. Many other frameworks tie the Model to data access -- typically via the ActiveRecord pattern or a Table Data Gateway -- which completely ignores the fact that this is tying the Model to the method by which it is persisted. What happens later if you start using memcached? or migrate to an SOA architecture? What if, from the very beginning, your data is coming from a web service? What if you do use a database, but your business logic relies on associations between tables? While the aforementioned posts do an admirable job of discussing the various issues, they don't necessarily give any concrete approaches a developer can use when creating their models. As such, this will be the first in a series of posts aiming to provide some concrete patterns and techniques you can use when creating your models. The examples will primarily be drawing from Zend Framework components, but should apply equally well to a variety of other frameworks. Input Filtering and FormsIn most cases, you want your model to perform its own input filtering. The reason is because input filtering is domain logic: it's the set of rules that define what input is valid, and how to normalize that input.
However, how does that fit in with forms? Zend Framework has a
What if you were to instead attach the form to the model?
Some argue that this violates the concept of "separation of concerns", due
to the fact that it mixes rendering logic into the model. I feel this is a
pedantic argument. When attached to a form, Basically, this approach helps you adhere to the DRY principle (one validation/filter chain), while simultaneously helping you keep a solid separation of business and view logic. Finally, you gain one or more form representations of your model, which helps with rapid application development, as well as providing a solid, semantic tie between the model and the view. So, on to the technique. Attaching Forms to Models
What I've been doing is adding a class Spindle_Model_Bug { protected $_forms = array(); public function getForm($type = 'bug') { $type = ucfirst($type); if (!isset($this->_forms[$type])) { $class = 'Spindle_Model_Form_' . $type; $this->_forms[$type] = new $class; } return $this->_forms[$type]; } public function save(array $data) { $form = $this->getForm(); if (!$form->isValid($data)) { return false; } $storage = $this->getStorage(); if ($form->getValue('id')) { $id = $form->getValue('id'); $storage->update($form->getValues(), $id)); } else { $id = $storage->insert($form->getValues()); } return $id; } } As the above code snippet demonstrates, the form acts as an input filter: you use it first to ensure the data provided is valid, and then to ensure the data you pass to your persistence layer is normalized according to your rules. You can also use it to verify the existence of certain optional values, as done here, in order to ascertain the actual action necessary to persist the data. What Happens in the Controller and View?Within your controller actions, you then have a slight paradigm shift. Instead of validating a form and then passing filtered data to the model, you simply attempt to save data to the model: class BugController { public function processAction() { $request = $this->getRequest(); if (!$request->isPost()) { return $this->_helper->redirector('new'); } if (!$id = $this->model->save($request->getPost())) { // Failed validation; re-render form page $this->view->model = $model; return $this->render('new'); } // redirect to view newly saved bug $this->_helper->redirector('view', null, null, array('id' => $id)); } } There's very little logic there, and no mention of forms whatsoever. So, how do we actually render the form? Note that the model is passed to the view -- which ultimately gives us access to the form. $form = $this->model->getForm(); $form->setMethod('post') ->setAction($this->url(array('action' => 'process'))); echo $form; This makes semantic sense; you're rendering a form that will be used to filter data for a given model. Note that some view logic is given -- the form method and action are set here in the view layer. This is appropriate, as we're now performing display-related logic. SummaryThere are of course other ways to solve the problem, but this is a convenient and expedient solution that maximizes use of the various existing components. Attaching forms to your models keeps all logic related to input validation -- including error reporting -- in one place, and ensures that your forms do not go out of date when you change your model -- as you will be updating your validation rules and list of allowed input in the form itself. In the next post, we'll look at using and applying Access Control Lists (ACLs) in your models. Trackbacks
Trackback specific URI for this entry
No Trackbacks
Comments
Display comments as
(Linear | Threaded)
How to recomend use Zend_Form in the model?
The form is of view!! Did you not read the post? Zend_Form does several things. First, it aggregates elements, metadata, and decorators. Each element aggregates metadata, validation and filter chains, and decorators. The decorators are used to provide a representation of the form -- but you are *never* required to render the form. As noted in the post, if you *do* choose to do so, the appropriate place to configure decorators and view-specific settings of the form is in the view script.
In you *models*, you simply use the form object as a filter chain, plain and simple. In this way, it acts as a substitute for Zend_Filter_Input, with the added bonus that you do not need to duplicate logic when building your form. That's actually really smart Matthew, and one super bonus side effect is that it makes your model (or more so) between applications, so dropping in a user/user_auth model into your app could be really simple. People might get confused because it says Zend_Form, they think views, but really zend_form is only partly about the actual display, its more about data input really.
Putting the validation logic in the model and not repeating it elsewhere makes a tremendous amount of sense to me.
I've been following your commits on the bugapp branch of pastebin and studying your code. I've tried to run the model tests (to try and get an idea how much is working) but run into a number of problems, some of which I've worked around (missing ini files, role isn't setup by test, couldn't find Model class) and others that stopped me, such as validation errors. I can tell by the frequency of commits that you are still actively working on it I look forward to continuing to learn from your efforts. Happy Holidays, --Rob As with Rob I've been following the commits to bugapp which I've found to be an invaluable source to learn from. I've already started to use Zend_From as described in the above, though i haven't been passing the model to the view, rather setting up the form in the controller and passing it to the view. I think I'll follow the above now and pass the model to the view.
Thanks. Merry Christmas, Gerard Matthew,
Great post, especially the fact that there will be a series how to create your models. I found out that's where developers, like me, feel the real pain in the !@&(!%*! Looking forward to your next posts. Continue the good work! Regards, TJ. This is really informative, Matthew. I feel the ZF community needs many more posts like this about models. I will look forward for the remaining posts in this series.
Matthew,
After reading your post a couple of times, I feel that you are hard coupling your validation with your domain objects. Why does a Bug or User object need to provide a getForm() method? Isn't it better to use a intermediate object (perhaps something like a datamapper) that provides a method for retrieving the form for validation and have your controller use that intermediate class to validate and retrieve the input data and form? Regards, TJ. I'm failing to see why validation would not be part of the domain logic. You want to have validation occur when you perform an operation that would alter the model; making the controller responsible for pulling the validator and using it means that you could potentially have code that calls the operation _without_ first validating.
Actually I'm not saying that you should pull the validation out of the domain. I also believe validation belongs to the model.
I just think you should put it in an intermediate class (that is part of the domain!) and not bother domain objects such as User or Bug with methods regarding validation/getting form, etc. It's the intermediate class that handles the validation and that also holds the form. Regards, TJ. You are talking about separating business logic from storage (which with i totally agree) but you tie your business logic input data validation to web forms. It's not the model validation rules you are using. It's the web form validation rules you are using. They _might differ. Maybe just an idea .. It would seem better to attach the _validation rules_ from the form to the model like $model->setSaveValidator($form->getValidator()) and still allow the model to have it's own validation and normalization not dependant of the of web form validation. This also keeps the bad guys doing something not proper with the form in the model.
And i don't really prefer passing model to the view. It just opens the doors for others to do quick & nasty stuff. Within the model, I'm simply using the form object as an input filter -- it's really no different than using Zend_Filter_Input. I can honestly think of no situation where the input filtering rules would differ between data submitted via a web form and, say, data submitted via a service (and, in fact, I have both in some applications I'm testing these ideas on). Now, as to escaping _output_, yes, these will differ -- but that's the realm of the view. And that's why I have the _view_ manipulating the _form_ object which it _pulls_ from the model.
As for assigning the model to the view, this is actually classic MVC. I agree with you that it can open the door for malicious actions on the parts of those working with the views, but that's a case for discipline, IMO. That said, it's also none too difficult to add functionality to the model that makes it immutable, and I will likely cover that situation in a later post.
Thanks Matthew. The ZF community indeed needs more articles like these.
One tiny remark: In keeping your views as focussed as possible, wouldn't it be better to assign just the form to the view? ( e.g. $this->view->form = $model->getForm(); ) Yeah, I go back and forth on that one. Pushing the model to the view script allows me to present multiple aspects of the view -- I might want to show the form, but I may also want to show a list of bugs, or a set of comments, etc. But when only the form is being displayed? Not entirely necessary.
Determining what to fetch and display in the view script - isn't that doing controller stuff in the view?
This boundary between what to display and how to display it is always a bit fiddly for me - do you want to change both your controller and your view if you just want to add a simple thing? My approach to it is to put as much as possible into small units that can be combined together on a page - no comments in the form view template, but both a form view and comment view on the page. There are as many approaches to MVC as there are PHP developers, and almost as many that are semantically correct according to the MVC definition. Manipulating the form's method and action is definitely view logic; how the form ends up in the view is another thing entirely, and will vary from developer to developer.
i don't understand why filtering is any concern to the model. if unfiltered data is getting that far, then there is something wrong.
Domain objects should be running in a black box that is free of anything that is not running in this sphere. Except maybe table/row-data-gateway and activerecord'ish methods, since they are even one layer deeper. what a model does is validating its input. if the input does not conform to standards of the business rules the proposed change in the model is rejected, nothing more. you say you want to let the model "perform its own input filtering". this sounds like you are doing it already in another place, the controller. duplicating this code leads to hurting the DRY principle. additionally since the controller handles the request and response and not the model, you can still hand non-filtered data to the view via the controller. If you put in filtering mechanisms there too, you have two layers of application doing the same stuff, which hurts understandability. saying "you dont have to render the form in this place" is bs imho, since the model and form are tied anyway! you rely on the view methods of the form implicilty and you open up the model API to access the views directly and circumvent the controller and its only a short way until someone not that well established with the implicit boundaries of the API doing $this->getForm()->getView()->doSomething(). i cant see the benefits here. Ben -- many people validate the form in the controller, and then pass the data to the model. We even show such examples in the ZF manual, for better or for worse. I'm simply trying to show an expedient way to move the validation to the model while retaining many of the benefits of Zend_Form (central location for input filtering, renderable web forms, logical groupings of elements). The benefit then is that you reduce the coding in your views, as error messages are collated with their associated elementsAdditionally, it _adheres_ to the DRY principle, as you're no longer doing validation in two separate locations. This allows you model to perform input filtering when in non-web contexts, such as when being consumed as part of a service layer.
As for your assertion that tying a form to a model implicitly ties the view to the model... you're right. It's then up to the developer to do the right thing, and not attempt to do view related actions within the domain logic, and vice versa. When the form object is being used inside the domain model, it's an input filter; the view can _optionally_ pull the form object and render it, at which point it becomes a web form. Again, this is simply *one way* to accomplish the goals (moving input filtering to the model, and keeping the application DRY). It's a practical and expedient method. If you don't like it, don't use it. I must admit that I don't really know if you use the term "Model" adequate to the term "Domain Object" of a domain model a la Fowler.
If the latter is true (which I don't think because ideally a domain object does not know anything about persistance), then I must say that Ben was right: a domain object never gets invalid data, that has to be done before. An domain object (e.g. a User) is something that is always valid, otherwise it would not "exist" (be in a valid state), so somebody else has to guarantee that the parts needed for creation of such an object are valid. And filtering is one level higher then validation, anyway. Validation in only one place is (almost) impossible if you want a "clean" domain model, following SRP. It's not a responsibility of the domain object and otherwise the ui/frontend does not know what the rules are and how to check them. I see your post here more like the intermediate objects Taco mentioned, or call it (Domain) Service, they prepare everything to finally create perfectly valid domain objects. A possible way to go is to construct a domain object exclusively with typed value objects (Fowler's ones - not Java's), they would know how to validate itself and could be queried by the UI and used in the domain model. They reside in between those two layers. E.g. you would use a constructor on user like: class User { __construct(VO_Username $username, VO_Password $password, VO_Address $address) (...) } All this VO's are valid (and self-validating) objects by itself, otherwise they could not have been created. Also $address is an example of a somewhat more complex type, following rules like "at least zipcode, city and street must be supplied". You could call this classes from your controllers before activating the "save user service" and therefore have some kind of global validation (though there are harder scenarios...) Personally i think a mediator between Zend_Form and your model class is a good solution to matching those two components of different worlds.
The mediator takes both Form and Model as method parameters. It then filters and validates the Form, retrieves the data, passes them to the form, catches all exceptions from the model and reinjects them into the form using "addErrorMessage". This way the form profits from the model validation mechanisms without having to be integrated with the form. I have written an example on my blog: http://www.whitewashing.de/blog/articles/109 My forms/models entry has provoked a ton of discussion. I think there are a few points to be made, though. First, the form object itself is not intrinsically part of the View; it contains a mechanism whereby it may be rendered, but this mechanism is opt-in, and generally should only be invoked from the view layer anyways. Second, the solution I blogged is intended to simplify the application layers. Solutions such as the one you propose add another layer of abstraction and indirection to the problem of validating model data; using the form object manages to keep the logic relatively simple while simultaneously fulfilling the requirements.
That said, I completely understand why some of you do not like the idea, and in fact have been in active dialog with Cal Evans to come up with another solution. It actually utilizes ideas from both Jani's proposal -- injecting validators from the model into the form -- as well as some that you suggest in your own blog. The basic idea is that the validation and filtering logic are model-specific, and thus on a failed validation, you inject that information into the form. The way to do this is to refactor Zend_Form_Element such that you can attach validation and filter chains to it (which is a better solution for Zend_Form anyways); this then allows you to take a failed model operation, pull the chains from it, and inject them into your form -- which gives you all the benefits of Zend_Form's error and value rendering. It also gives you the ability to pull validation and filter chains from a form and inject them into your model -- basically, it's a more flexible approach, and keeps the form and model logic separate, while allowing each to benefit from the validation and filter chains. While this method can't possibly be the only way to do it, one thing it will accomplish is keeping controllers thin. In my experience, the views stay pretty thin but then all the logic gets stuck in the controllers, which ends up making the app less reusable.
Even if you don't use the form aspect of it, at least using zend_validate in the model and keeping your filtering in one place will make it a lot easier for maintenance and tracking down bugs. Matthew,
How would you handle multiple forms? For instance, I have a user-login form with only two elements/fields and a user-create form that has significantly more elements. I thought about it, but to remove all non-relevant elements in the view seems rather cumbersome. Would you request a different form from the getForm() method in the model for this? Regards, TJ Yes, exactly. The getForm() method in the spindle User model right now accepts a parameter, the form to load; it then maintains an internal registry of forms (login and registration).
Matthew,
Just one more question about using Zend_Form as an input filter. What if you have specific form elements (like a token) that need to be validated? Where would you put the validation? It seems odd to that in the domain model, which it's not part of. Regards, TJ. Add it in your view layer ($form->addElement('hash', 'token')), and mark it to ignore the value when pulling values: $token->setIgnore(true); By adding it during the view, you're telling it that it semantically belongs to HTTP (since the view is HTML and transmitted over HTTP; by marking it to ignore, it won't be retrieved when you call getValues() on the form (but will still require validation).
I'm a bit confused, would you validate a token in the controller?
In my setup I can't seem to retrieve the error message when an invalid or missing token is supplied, since I do all the validation in the model. How would you retrieve the error? Actually... I misled you with my last comment. The token *must* be part of the form if you're going to both have it present in the rendered form as well as when you validate. However, you can use the ignore flag to ensure it is never returned when you call getValues() on the form object.
This may be unrelated, but I saw Zend_form and had to ask this. I have been putting all form variables into a MY_DEFAULT_FORM class which extends Zend_form and then I have been using an array to select which components to add for whatever form I want to present. So basically I have
<?php class My_Form_Default extends Zend_Form { public $login; public function deafultforms($login) { every $this->createElement (array(validators)) for the site $dlogin = array($login, $variables); $dreg = array($registration, $variables,$etc); if($login == 'login'){ return $this->addElements($dlogin); }elseif($login == 'reg'){ return $this->addElements($dreg); } } } and in the the controller or like in this case model I can put $form = new My_Form_Default(); $form->setaction('$this->url(array('action' => 'process''); // post is on by default $form->deafultforms('reg'); // add the submit button $form->addElement('submit', 'submit', array( 'label' => 'Register', )); $this->view->form = $form; // could just add the submit button variable with an array function for text in class It is almost the same jist of what your getting at I think. I am definately sure I can trim it down some. I use this because why write a registration form and a login form that has both the same validators. But still learning thanks for your posts. Hello,
If I have a model for Comments, when a user creates a new comment where would I securely capture data like current user id, session id and user ip under the hood while still being able to run element filters/validators on each? The way I have it now, the comment create form renders a text field for each user id, user ip and session id. I want to capture these without the users interaction but still be able to display form errors if any of those fail validation? How do you reccomend I do this? Thanks again How do you deal with different contexts when saving data?
For instance if you have a User Model where users can register and admins can create new users. Say the admin can set the user role, but when registering the role should be defaulted to something and not required. Or when editing you may have more fields available. So you see then you have three contexts registering, editing and adding. Would you have three different forms for this? I hope this makes sense, having a hard time explaining my problem I really appreciate your vision, but there's something about this topic that i need to argue with you.
You said it's better to move forms inside the model and delegate the validation to them, but if you have more than one form in a model (user login, user registration, user subscription, etc), you need to attach the same validator for the "username" field (as a example) in each form, so there's repetition here!. I suggest to attach validators at model level, and let the forms use this validators when they needed them. Oops, i forget to tell another use case:
What happens if you've got a model and this model is only called via api or webservice. Without html form you need to setup something like a form (the validation part) inside the model. Ok, let's do some code: class CarModel extends ObjectModel { protected $data = array('id', 'name'); protected $validators = array(); public function __construct() { $this->validators['id'][] = new Zend_Validate_Int; bla bla... } public function isValid() { // traverse all validators to check if model properties are valid } ... } So, if you setup a Car form (register car, for example), you can do this: class RegisterCarForm extends Zend_Form { public function init() { $carModel = new CarModel; $name = new Zend_Element_Text; $name->addValidators($carModel->getValidators('name')); ... } } So, all your forms regarding cars will use the model's validators. If in any form you will extend/change any validator there is no problem as the base validators they always come from model. This is an idea I've been experimenting with with Cal Evans recently as well. The idea I'm considering is allowing the ability to attach an entire Validator _chain_ to the element (and the same with filters). This would simplify the form API, and add more flexibility. However, it cannot really happen properly until 2.0 as it would also break backwards compatibility.
Maybe this is the time to start thinking about developing a Zend_Model abstract class, providing filters, validators and common methods, so forms can interact with models.
The problem before was to deploy an empty Zend_Model, but with this new 'features', a rich Zend_Model could be a good startpoint to developers. Matthew,
I apologize if you have already answered this question or if it just shows ignorance. I have to deal with many web forms and many of the forms have their own set of html elements, but most have the same elements basic elements. Should I create a class which extends zend_form and has all of my elements in it and then each time I need a form, I would pass the necessary elements in an array to the constructor. Is there a way to iterate over the form elements to make this easier. Thank you for any light that you could shed. Hello Matthew,
since I am quite new in this field, but alwas wondering on how to sort model, view and control (very happy about finally a Quickstart on ZF _with_ a model), I just would like to drop in a question: isn't this whole "problem" a result of Zend_Form being not SRP? Zend Form does Validation (which is, why one/you should put it in model) _and_ it creates HTML, (which is, why it belongs to view too). Model should know, how to validate. But it should not care about, if a multi-selection should be displayed as multiple selctbox or a set of checkboxes, neither if a textfield should be marked up with stars (type=passwort), should it? So a Model (interface) should maybe/ or must pass some model-information to the view, since view must know, that there is e.g. a single-choice-list. But the (View_Helper_Form ???) should then configure, how to display. But then you would need to different form-classes. One for validation only (Model_Form) and one for View. Hope to have made this idea somehow clear, Btw: retreiving the whole model in the view, wouldnt that mean, that controller just gets rid of his responsibility (by giving the view [too much?] control? Add Comment
|
Calendar
QuicksearchLinks
CategoriesSyndicate This BlogShow tagged entries |
|||||||||||||||||||||||||||||||||||||||||||||||||




