Is it OK to have content negotiation in your controller layer? - php

In Zend Framework 2, content negotiation happens on the view layer and I am pretty happy with it. An example in my controller:
public function viewAction()
{
$id = $this->params('id');
$user = $this->getRepository()->findUser();
return new ViewModel(array(
'user' => $user,
));
}
This either renders the view.phtml template to return html or it converts the user object to a JSON response. A so-called view strategy determines how to render the response based on the request.
"REST" Application Flow in my webapp
This type of content negotiation works pretty good for many use cases:
/user or indexAction() returns an array of users => html of JSON possible;
/user/1 or viewAction() returns user object => html or JSON possible (example from above);
/user/1/update or updateAction() returns a html form. A POST to this url returns html or JSON when errors are present. Or on success it redirects to viewAction() and thus returns the new version of the user => html and JSON again possible;
/user/create or createAction() returns a html form. A POST to this url returns html or JSON when errors are present. Or on success it redirects to viewAction() for the just created user => html and JSON again possible
My Question
There are a few use cases where the content negotiation is sort-of "required" in the controller layer. I am not sure if I overlook some possibilities: are there options I can use in for example the following cases?
Delete a user: POST to the /user/1/delete. In case of a html view, you will be redirected to the list of users (where the deleted user is now missing). In case you want a JSON response, you want to return 200 OK and a JSON object with a message the delete was successful.
Post a comment to a blog article. In case of a html view, you will be redirected to the post where you see your comment is appended. In case you ask for a JSON response, you want to return 200 OK and a JSON object with the comment you just placed.
My goal would be to not replicate the content negotiation already present in the view layer. It would also make my controllers more fat, since I have now two possible responses (JSON vs html) but that might not be the only case. If I later want to support XML or another format, I have for every action switches for those response types.

Interestingly, we're looking currently at moving the content negotiation aspect out of the view strategy listeners, and instead into controller plugins. The rationale is largely as you note -- it's the controller's job to match an incoming request to the appropriate model(s) and view. As such, yes, I think you're on the right track -- and likely the tools being developed now for 2.1 will fit the methodologies you have quite nicely. (See https://github.com/zendframework/zf2/pull/2615 for details.)

Related

Laravel View::make after filter?

Is there a way to someone have an after filter for the View::make? What im trying todo is to run the content from View::make that is returned, through an HTML minifier.
I already have App::after that minifies the final html doc. But see, im putting the View::make response into a json object (for ajax requests) and i need the response to be minified.
After filters generally work after the response has been sent to the user.
So to minify the HTML that the user will see need to be processed before it is sent.
But before filters will not work here either. As they are called before the controller method is processed.
So you will have to your process to be used within your controller, a possible solution could be to use a helper function with your minify code, or as a function within your BaseController, which is accessible to all your controllers which called the helper function.
You can do the following within your controller;
$view = View::make('view.path', $data)->render()
This will render and process the view into the HTML the user will see.
You can then pass this to the function you have to minify the HTML and insert it into the JSON response.

Best Practices and how to find get POST data from iOS AFNetworking in Symfony2 and return JSON in GET?

I am building a mobile app (iOS) and Symfony2 REST backend. On Symfony2, my routes are working correctly and I have tested them with AJAX and httpie, all CRUD operations, etc are fine. Now, I am trying to access the routes from the app. So far, I can access the routes and when I look into the Symfony2 Profiler, I can see entries in last 10 entries to verify that I am hitting the server with my POST and GET requests. Now, I have 2 questions and I would be glad if people can point me in the direction for ** Best Practices ** on how to proceed.
Problem 1: Although I am posting data which I can see coming in under "Request", when I try to create a record, it creates only NULL records, meaning the data is being lost. This is my controller for creating users for example:
public function postUserAction(Request $request)
{
$content = $this->get('request')->getContent();
$serializer = $this->get('jms_serializer');
$entity = $serializer->deserialize($content, 'Name\BundleName\Entity\User', 'json');
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
return array(
'entity' => $entity,
);
}
When I look into the log, the only things that stand out are: Request Cookies (No cookies), Request Content: "Request content not available (it was retrieved as a resource)." This tells me the data was missing, how can I get this data and use it? Or what else could it be?
Problem 2: GET returns an empty JSON response with no data just the keys when I NSlog (echo it). My code looks like:
public function getUsersAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('NameBundle:User')->findAll();
return array(
'entities' => $entities,
);
}
From the log, it has the Request Cookies set: PHPSESSID => "1udududjjs83883jdlb4ho0j4" but again the Request Content says: "Request content not available (it was retrieved as a resource)." How can I make it return the data with the JSON? This works well in the browser AJAX and httpie tests.
Problem 3: Using AFNetworking, I have a symbolic constant which I set as the APIHost (IP Address) and APIPath was the folder. Now in my earlier version using native PHP, I constructed the actual code to be executed in index.php by sending the parameter in JSON so if I wanted a login, I sent something like todo:login but with Symfony2, I am not sure or know even the best practices for this case. Ideally, I would like to specify the server-side request in the JSON request and then find the correct route in Symfony2 but is this how to do it and if yes, can you please provide an example? The workaround is to specify hard coded paths in AFNetworking each time I need to make a request which I think tightly couples the code and I need to make changes in a lot of places anytime something changes on the server side. Thanks and sorry for the long question!
You expect the jmsserializer to do magic for you. But it won't, you have to configure it first. From you code I can see that you are using jmsserializer wrong.
In getUsersAction() you have to return a serialized response, but you are returning an array of objects. This would be the right way:
public function getUsersAction()
{
$em = $this->getDoctrine()->getManager();
$entities = $em->getRepository('NameBundle:User')->findAll();
$serializer = $container->get('jms_serializer');
return array(
'users' => $jsonContent = $serializer->serialize($entities, 'json');,
);
}
Your post action basically looks ok, however when the json does not contain every field of entity USER the deserialization will fail. You can configure the entity for serialization/deserialization using annotations.
http://jmsyst.com/libs/serializer/master/reference/annotations
I am not sure if I understood your last problem, but I think you have to hardcode the path in your app.
Symfony2 is great and absolutely useful when writing an API. But if you don't want to deal with serialization/deserialization you can give http://laravel.com/ a try. It is build on symfony and you can generate an api on the fly.

Symfony2 POST Request Required, Receiving GET Instead

I am trying to implement a search module with clean URLs. Something like www.website.com/search/searchterm. I have made a searchable index with EWZSearchBundle, so there is no database involved and therefore, no entity required.
public function searchAction(Request $request)
{
$form = $this->createFormBuilder()
->add('query', 'text')
->getForm();
if('POST' === $request->getMethod()){
$form->bind($request);
if ($form->isValid()) {
return $this->redirect($this->generateUrl('search_process', array('query' => $request->query->get('query'))));
}
}
return array(
'form' => $form->createView(),
);
}
I created a simple form without an entity and sent the form action to itself. Where it reads a if it a POST request I validate the form and send it to search process with a clean URL (www.website.com/search/searchterm).
public function searchProcessAction($query)
{
$search = $this->get('ewz_search.lucene');
$results = $search->find($query);
return array(
'results' => $results,
);
}
In the search process I get the search term from the clean URL and search for it in my index and return the results. It should be a very simple process only one problem.
Since I don't need to use an Entity, it never becomes a POST request and never gets inside the if('POST' === $request->getMethod()), and now that it becomes a GET request, it also spoils my whole thing about keeping the URL clean.
I know my way makes and extra redirect, but I don't know how else to keep a clean URL for search. I'm open to any suggestions about the whole process.
Some thoughts:
by a rule of thumb, a search action should be performed via GET method: you aren't creating anything, you are just querying your site for some results;
though clean URLs are nice and all, search functions should still take advantage of good ol' query syntax [http://path.to/search?q=termToSearchFor]; this way query string never gets cached, and you are sure to always fetch updated content [without needing do specify cache behaviour server side];
if your concern is to protect your data from certain traffic, consider implementing either authentication or a CSRF token in the form.
regarding this:
Since I don't need to use an Entity, it never becomes a POST request and never gets inside the if('POST' === $request->getMethod()), and now that it becomes a GET request, it also spoils my whole thing about keeping the URL clean.
This is just plain wrong: a POST request has nothing to do with Entities, it's just a mode you specify in request headers, in order to ask the server for a specific behaviour.
Your url will still be "clean" if you define it as /search/{query}, and update you action as follows:
public function searchAction($query){ ... }
But as I said before, query syntax is perfectly fine for search behaviour, and POST should not be used for such task.
A smart reading about RESTful principles - http://tomayko.com/writings/rest-to-my-wife .
You have to submit your form with POST method.
in HTML
<form action="YOUR ACTION" method ="post">
if you want to be sure that no one will come on this link in some other way (GET), then modify routing
rule_name:
pattern: /search/{query}
defaults: { _controller: AcmeBundle:Search:search }
requirements:
_method: POST
I managed to get it working without using the form component. I made the form manually, also accepted the query string format suggested by #moonwave99. Using the form component gives longer names like form[query] and form[_token] where it sends that form's CSRF token in the URL. Making the form manually allows better control on URL for query string format.
Note: Beware that at the same time, it removes CSRF security from that particular form.
Thanks for all the answers.

get object data in mvc structure

I'm working with a PHP MVC Framework. Works really well. I like the separation of the business layer (model) with the business logic (controller). But i just stumbled upon a problem. Here's the thing:
Suppose i navigate to the following url:
http://localhost/user/showall/
In this case the userController.php is called and within that file there is a method showallAction() which gets executed.
In the showallAction() method i simply do a request to a model which gets all the users for me. Something like this:
public function showallAction()
{
// create userModel object
$users = new userModel();
// get all users and assign the data to a variable which can be accessed in the view
$this->view->users = $users->getAllUsers();
// render views
$this->view->render();
}
So this method gets all the users, assigns the data returned from the userModel to a variable and i can easily work with the returned data in my view. Just a typical MVC thing.
Now here comes the problem.
I also need to create a native iphone variant. Ofcourse the looks will be totally different. So all i actually want to do is to request this url:
http://localhost/user/showall/
And that it just gives me the array (in json format) back. So i can use that for the mobile development.
But this obviously can't be done right now because the showallAction() method assumes that it is for web browser display. It doesn't echo JSON formatted, instead it simply assings the array of users to a variable.
So that means i have to create another method "showallMobileAction()" in order to get the data, but specifically for the mobile device. But this is not an elegant solution. I'm sure that are better ways...
Anyone any idea how can i solve this problem??
In your situation i would modify the routing mechanism.
It would be useful, if you could add extension at the end of URL, which represents the format you expect, like :
http://foo.bar/news/latest >> HTML document
http://foo.bar/news/latest.html >> HTML document
http://foo.bar/news/latest.rss >> you RSS feed
http://foo.bar/news/latest.json >> data in JSON format
It's a simple pattern to recognize. And you can later expand this to add .. dunno .. pdf output, or Atom feeds.
Additionally , two comments :
Model is not a type of objects. Instead it is a layer, containing objects responsible for business logic, and objects responsible for data storage/retrieval.
View should be a full blown object, to which you bind the domain objects (objects responsible for business logic).
You could pass parameters to your url:
/user/showall/json
and get the third URL segment with a custom function or a built-in one. For instance, with CodeIgniter: $this->uri->segment(3).
Some frameworks will pass the additional parameters to your method. Just try this with the URL I wrote above:
public function showallAction()
{
print_r(func_get_args());
}
I'm not familiar with PHP MVC but in general terms I'd use the "accepts" HTML header field to request the response in either "text/html" or "text/json", the controller would check for the accepts type and return the response accordingly.

How to handle AJAX request in my PHP MVC?

I am in the process of learning the MVC pattern and building my own lightweight one in PHP
Below is a basic example of what I have right now.
I am a little confused on how I should handle AJAX requests/responses though.
In my example user controller below, If I went to www.domain.com/user/friends/page-14 in the browser, it would create a User object and call the friends method of that object
The friends method would then get the data needed for the content portion of my page.
My app would load a template file with a header/footer and insert the content from the object above into the middle of the page.
Now here is where I am confused, if a request is made using AJAX then it will call a page that will do the process over, including loading the template file. IF an AJAX call is made, I think it should somehow, just return the body/content portion for my page and not build the header/footer stuff.
So in my MVC where should I build/load this template file which will have the header/footer stuff? ANd where should I detect if an AJAX request is made so I can avoid loading the template?
I hope I am making sense, I really need help in figuring out how to do this in my MVC I am building. IUf you can help, please use some sample code
/**
* Extend this class with your Controllers
* Reference to the model wrapper / loader functions via $this->model
* Reference to the view functions via $this->view
*/
abstract class Core_Controller {
protected $view;
protected $model;
function __construct(DependencyContainer $dependencyContainer){
$this->view = new Core_View();
//$this->view = $dependencyContainer->get(view);
}
public function load($model){
//load model
//this part under construction and un-tested
$this->$model = new $model;
}
}
user controller
/**
* Example Controller
*/
class User_Controller extends Core_Controller {
// domain.com/user/id-53463463
function profile($userId)
{
//GET data from a Model
$profileData = $this->model->getProfile($userId);
$this->view->load('userProfile', $profileData);
}
// domain.com/user/friends/page-14
function friends()
{
//GET data from a Model
$friendsData = $this->model->getFriends();
$this->view->load('userFriends', $friendsData);
}
}
For me, I developed a separate object that handles all template display methods. This is good because you can then ensure that all the resources you need to display your UI is contained in one object. It looks like you've isolated this in Core_View.
Then, when an AJAX call is made, simply detect that it is an AJAX call. This can be done by either making the AJAX call through an AJAX object, which then references other objects, or you can take an easy approach and simply set an extra POST or GET field which indicates an AJAX call.
Once you've detected if it's an AJAX call, define a constant in your MVC such as AJAX_REQUEST. Then, in your template/UI object, you can specify that if it's an AJAX call, only output your response text. If it isn't, proceed with including your template files.
For me, I send it through an AJAX object. That way I don't have to worry about making a single output work for both cases. When it's ready to send a response, I just do something to the manner of print( json_encode( ...[Response]... ) ).
well, it would all start with normal request which would load the initial page. there are many options as to handle this but let's say that you start with /users/friends page which would list all your friends. then each of the friends should have link to specific friend's profile -- now this is the moment where ajax could kick in and you could ajaxify links to your friend profiles - this means that instead of normal you would instead use let's say jQuery and setup click handler in a such way that
$("a").click(function(){$.post($(this).attr("href"), null, function(data){$("#content").html(data);}});
this would use "href", and upon click would make post request to your backend. at backend, if you see that it's post, then you would just return the content for that particular friend. alternatively, if you have get request, you return all - header - content - footer.
if you use technique above, make sure to properly handle the data you receive. e.g. if there are further actions that should be done via ajax, make sure to "ajaxify" the data you get back. e.g. after updating html of the content, again apply the $("a").click routine.
this is just trivial example, to kick you off, but there are many more sophisticated ways of doing that. if you have time, I suggest reading some of agiletoolkit.org, it has nice mvc + ajax support.
You will need to use a different view. Maybe something like:
funciton friends() {
$this->view = new Ajax_Request_View();
$friendsData = $this->model->getFriends();
$this->view->load($friendsData);
}

Categories