Everyone understands mvc as he wills, but sometimes I do not, at all.
Currently the majority of my controllers look like this:
<?php
$input = $_REQUEST['field']
$model = new Model();
$status = $model->launchSpaceShuttle($input);
switch($status)
{
case Model::STATUS_LAUNCHED:
header('Location: Mars');
break;
case Model::STATUS_INVALID_INPUT:
echo "Please press the big red button correctly";
break;
case Model::STATUS_PILOT_IN_HANGOVER:
...
...
case etc.
}
This leads to the obvious question:
Is this how it's supposed to be? Model's returning status codes and controllers deciding how to display and what?
Because this very much conflicts with the theory about simple, short controllers that act as a really thin wrapper between the input device and the model.
bonus case: what about ajax requests if I'm too lazy to create a view ;
Ah, that's easy. Just move the entire switch to a view file.
It's the view's responsibility to interact back with the user.
Redirecting from inside the view is just as fine.
Anyway in most frameworks the view file inherits the $this variable which represents the active controller.
Same regarding ajax requests.
Related
Using MVC I have something like this:
class Controller
{
//returns View
function indexAction()
{
$data = $this->getData($this->id);
$view = new ViewModel();
$view->setVariable('data' => $data);
//used to render HTML template + data above later on
return $view;
}
//gets data from DB
//currently also does business-proprietary computation on data
function getData($id)
{
//repository/dao pattern
$data = $this->repository->getData($id);
//Business Logic "derivation"
foreach ($data as $datum)
{
//that does not go into "Controller
//that does not go into "Repository"
//but where does it go? - that's my question
$derivedData[] = (new Business())->doLogic($datum);
}
return $derivedData;
}
}
Recap
I used Controller to get my data out of DB using Repository pattern, then placed received data into view. But business-related computations are left stranded.
Question
Where do I place my business logic computations that act on the data gotten from repository? The derived data which is to return to Controller later, to be placed into View?
My personal choices of architecture are usually to:
Have small controllers as thin as I can, doing only session and general right checking
Services that are handling all business logic, one (one classe yes) per potential feature I need
Services are querying repositories, and eventually manipulate the data in and out, but usually no Controller, nor view will do a ->save() anywhere.
This means that those services are usually designed to be independent from the database and easier to be tested because they only take care of one and only one task.
In your example, the whole function getData will be a service that I would call GetCarDataById. This assuming that you manipulate Cars, I don't like to leave data wandering alone.
EDIT: to make it clear, this kind of approach is not MVC to some definition, most people interpret MVC as putting all code either in controller, either in repositories (model). To others view, MVC doesn't mean that you have other classes, what I call services, and actually most of my code lives here.
The MVC pattern is clear for me.
From wikipedia:
The model directly manages the data, logic and rules of the
application. A view can be any output representation of information,
such as a chart or a diagram. Multiple views of the same information
are possible, such as a bar chart for management and a tabular view
for accountants. The third part, the controller, accepts input and
converts it to commands for the model or view.
Answering your question. The modifications goes in the model domain.
I wonder what would be the professional way to handle insert/delete requests of a website?
Right now, what I have is I inserted two <input type = "hidden"/> on each form where one hidden's value correspond to a function it needs and the other is the parameter of this function. So when the form submits, I have a post.php file that handles ALL insert/delete requests that simply invokes the value of the hiddens via call_user_func() in PHP. Like so:
<input name = "arg" value = "{$id}" type = "hidden"/>
<input name = "call" value = "delete_member" type = "hidden"/>
call_user_func($_POST['call'], $_POST['arg']);
I'm having doubts on how sensible this solution is because I found out that the hiddens aren't actually hidden in the source on the client-side.
My first solution was to basically have a lot of conditionals checking for which function to invoke but I really hated that a lot so I changed it with this solution.
I wonder what are the better ways I can do this, or maybe how the professionals do it? Handling insert/delete queries.
I would consider this a very bad way to call actions from the client side.
For one this data is put into the HTML which will always be viewable and editable by the client. As such this means you cannot trust the data you receive from the client and as such you cannot trust the function they are calling.
Another point to re-inforce my previous idea. You say you run validation to make sure it is a function and all that, but you have a problem. Closures return true on these functions (since they are functions and methods and they exist). So a user can put a anon function as the value of your hiden field and actually run whatever they want on your server.
As others say I would recommend looking into MVC. Look into how Yii/CodeIgniter/Zend/Lithium/Kohana/etc do this and how they route.
An example of how routing for actions such as deletion is done by my favourite framework, Yii:
class UserController extends CController{
public function actionDelete($id = null){
if($id===null){ return false; }
}
}
Then the form/link calls /user/delete?id=2 which makes index.php route to the userController and use the actionDelete function inside the user controller, running it's code. Of course this is a very simplified version and it gets a lot more complex to stop vulnerabilities.
You may also wish to look into CSRF validation.
The most common way is to just call a function that takes care of one form at the time like this example for submitting a blog message:
blog.php
if(isset($_POST['submit'])) {
save_message();
} else {
display_form();
}
function display_form()
{
?>
<form action="blog.php"> etc etc....
<?php
}
function save_message()
{
//security checks and inserts etc
$_SESSION['message'] = 'Form saved succesfully';
header('location: blog_overview.php');
}
This is according to me a practish, but you might want to checkout frameworks like Codeigniter and Kohana since the above code is functional (and to me outdated). Read some tutorials about OOP (Object Oriented Programming) and MVC (Model View Controller). It might seem alot of work, but if you really want it to do it right it is worth the time and effort.
In a MVC pattern, what's the best way to handle when a single view could have multiple actions of the same type (e.g POST)?
Say for instance in a TODO list application. You might allow a user to create multiple lists. Each list could have multiple items. So a user navigates to site.com/list/1 which shows them all the items on the 1st list (1 is a GET parameter). There are then 2 forms (POST) on this page to allow a user to:
Create a new item
Delete an existing item
Should the bootstrap create a "listcontroller", inspect the POST variables and then call the appropriate method similar to :
$lc = new ListController();
if(strtolower($request->verb) === 'post'):
if(isset($_POST['title'])) :
$data = $lc->newItem($_POST);
$load->view('newitem.php', $data);
else if(isset($_POST['delete']) && isset($_POST['id'])):
$data = $lc->deleteItem($_POST);
$load-view('deleteitem.php', $data);
endif;// End if post title
else:
//GET request here so show view for single list
endif; //
Or is it better to just do something like
$lc = new ListController();
if(isset($_POST)):
//controller handles logic about what function to call
$data = $lc->PostAction($_POST);
// $data could also potentially hold correct view name based on post
$load->view();
else:
//again just show single list
endif;
I'm just struggling how best to have a controller potentially handle multiple different actions, as there's potentially quite a few nested if/else or case statements to handle different scenarios. I know these would have to sit somewhere, but where is cleanest?
I know that there are many frameworks out there, but I'm going through the whole "want to understand best practice" behind it phase. Or is this totally the wrong way to do it? Should the controllers actually be structured differently?
To begin with, I actually really like, how you are dealing with implementation of MVC. None of that rails-like parody, where view is managed inside the controller.
Here is what I think is the root of your problem: you are still using a "dumb view" approach.
View is not supposed to be a synonym for "template". Instead it should be a full object, which has knowledge-of and ability-to deal with multiple templates. Also, in most of MVC-inspired design patterns, the view instances are able to request information from model layer.
In your code the issue can be traced back to view's factory ( the $load->view() method ), which only gets what controller sends it. Instead controller should only change the name of the view, and maybe send something that would change the state of view.
The best solution for you would be to create full-blown view implementation. Such that view itself could request data from model layer and , based on data it received, decide which template(s) to use and whether to require additional information from model layer.
I think you're somewhat on the right track with the latter approach. However, you should not hard code the calling of actions in your bootstrap. The bootstrap should interpret the URL and call the action methods dynamically through the use of a function like call_user_func_array.
Also, I would suggest that you leave the rendering of views up to the action code so the action logic is self sufficient and flexible. That would allow the action to analyse the input for correctness and render errors or views appropriately. Also, you've got the method 'deleteItem' on your controller, but that should really be the work of a model. Perhaps you should read up some more on MVC and try to work with an existing framework to understand the concepts better before you try to implement your own framework (I would suggest the Yii framework for that).
Here's an example of how I think your logic should be implemented in a good MVC framework.
class ListController extends BaseController
{
public function CreateAction($title){
if(ctype_alnum($title))
{
$list = new List();
$list->Title = $title;
if($list->insert())
{
$this->render_view('list/create_successful');
}
else
{
$this->render_view('list/create_failed');
}
}
else
{
$this->render_view('list/invalid_title');
}
}
public function DeleteAction($id){
$list = List::model()->getById($id);
if($list == null)
{
$this->render_view('list/errors/list_not_found');
}
elseif($list->delete())
{
$this->render_view('list/delete_successful');
}
else
{
$this->render_view('list/delete_failed');
}
}
}
here is a great tutorial on how to write your own MVC framework
I have been doing some reading about many different things lately to improve my coding quality.
I am wondering if it is a good practice to post form to the same page often I used this type of snippet in my current applications.
public function addAction(){
$form = new Application_Form_Add();
$this->view->form = $form;
if(!$this->_request->isPost() && $form->isValid($this->_request->getParams())){
$mapper = new Application_Model_ModelMapper();
$model = new Application_Model_Model($form->getValues());
if(!$mapper->save($model)){
$this->view->messages('an error occurred etc ... ');
return;
}
$this->_helper->redirector->gotoRoute(array('id' => $model->id), 'model_view');
}
}
so my add action presents the form on GET request and process it on POST request.
I came around this article of Matthew Weier O'Phinney, I think everyone can agree he is one of the PHP gurus of the moment. In his example, he make two differents action one to show the form, one to process it. So if the form doesn't validate he plays with $this->render to re render the form view.
Is it a bad practice to submit form to same page and if yes why?
For the purpose of code readability and maintainability, two different controller actions - despite them associated with the same form - are better separated as two functions/scripts.
Imagine what it would be like if all CRUD operations were combined in a single function/script. That will be messy.
I don't think it's bad.
No matter we do this with same page and different page.
Both ways are correct at their places.
using singe page is a sort and sweet way and using 2 pages gives our code a managed way.
In my opinion both are equall.
In my oppinion I prefer to use one action for it. This keeps all the stuff together. O'Phinney's controller could loo like this:
addAction (uses saveAction to save)
editAction (uses saveAction to save)
saveAction (does not render itself)
Having one method for all controller save/edit logic is the only reason (assumption!) to make it and extra action. Cool, keeps everything in one place. But: this required both, edit and add, to have exactly the same logic. As soon as there is any if/else like
public function saveAction()
{
if($action == 'edit)
{
// edit logic
} else if($action == 'add')
{
// add logic
}
}
to have a different logic depending on the action, this totally exploits the idea of one action. So if you know that the logic is always the same and is very unlikly to change, this is the way to save you lot's of coding work :).
On the other hand, if your add/edit logic are a bit different, I'd keep all logic together that belongs together:
addAction (prints form and saves on POST)
editAction (prints form and saves on POST)
This might look like code-duplication (and maybe is), but then, my edit/add actions have about 3-6 lines of code. But as soon as the logic of either changes, you really know the place.
So this is a personally oppinion, I'd stick with seperated actions as soon as stuff becomes more complex.
(As a note: to logic I'm refering to controller logic, all data-logic should be in the model anyway)
I would like someone to explain me what _forward is exactly doing, I cannot see if _forward is also rendering the attached view to the action or just executing the action.
Also is it possible to pass argument to $this->action in a view script ?
More generally my problem is how to code a confirmation page, let's say the user input some stuff and you want to show him confirmation, is forward is mean for that case ?
_forward is an internal redirect. Where as _redirect sends a header that tells the client's browser to go to some other URL, _forward tells the Dispatcher to internally redirect the request somewhere else.
If you consider the normal dispatch order of:
preDispatch()
someAction()
postDispatch()
Calling _forward at any point in that progression will cause the following steps to not be executed. So if you call _forward in preDispatch(), someAction() will not be called and so on. If you _forward() in someAction() and you are using the viewRenderer action helper to render your views (you are letting the framework choose what view script to render), then no view script will be rendered in someAction().
When the request is forwarded to the new Controller / Module the entire dispatch process will be repeated there.
You can find out what action is being dispatched by using:
$action = $this->getRequest()->getParam('action');
$action will be the url form of the action so if the method is name 'someKindOfAction', $action will contain 'some-kind-of'. You can do this as well for controllers and modules.
My experience with Zend is limited and I hope I'm not showing you something you've already seen but according to the docs (12.7.6. Utility Methods):
_forward($action, $controller = null, $module = null, array $params = null): perform another action. If called in preDispatch(), the currently requested action will be skipped in favor of the new one. Otherwise, after the current action is processed, the action requested in _forward() will be executed.
So it sounds like the context of when it's called matters. In the latter case it will first execute the action from which it's been called then execute the forwarded action. The exception is when it's being called from the preDispatch handler
I think it's important to note that _forward is very inefficient, and you should always call your method directly. When you do a _forward, the init(), pre and post dispatch run again. Depending on what you have in your init, you can run (and insert) the same database record twice.
It is easy to use but wasteful. If you profile your code, and are banging your head to why everything is being called twice, _forward is the reason. If your like me and you instantiate a few objects in the init() for use throughout the class, you wind up instantiating everything twice! I did load testing on my code and I got better performance by calling the action name directly, like foo(), instead of _forward('foo');
Another off topic tip I think most people know, is it use single quotes wherever possible, sine the PHP parser has to check a string for embedded variables. I don't know how much real world performance this will give, especially if you are using an opcode cache, but it's a best practice.
Forward is ment to be used when external redirect is not the right options.
Use case (bit ankward, but best i can make up):
You have a form that can add your pet (either dog or cat). You have different models for each. You include a select in your form to select dog / cat. Then in your action you do:
if($form->isValid($_POST)){
switch($form->select->getValue()){
case "dog":
$this->_forward('add-dog','pets','default');
break;
case "cat":
$this->_forward('add-cat','pets','default');
break;
}
}
And you handle different things for cats and dogs in separate actions. The advantage of this is that ALL the parameters are sent along. In constrast when you'd used $this->_redirect() the POST parameters will be lost. That is in some cases intended behaviour (for example after adding a comment you make a redirect to comments list page to avoid double posts and the message "page needs to send data again...".
A part of the Framework docs I swear used to be there explained the dispatch workflow at a general level. Theres this diagram, but its awefully complicated to explain what _forward does.
When in an action _forward will set $request->isDispatched = false, and set up the request to call the controller/action specified in _forward. During postDispatch, the isDispatched is checked - if its false, the whole thing runs again using the new request.
So... If in your action you're manually rendering views, they'll still get rendered. Everything in the action will still happen, its just another action will ALSO happen afterwards.
[edit after question edit]
Forward is not meant for the response/confirm-after-post - use a redirect for that. $this->_helper->redirector->gotoUrl() etc.