Pass user-supplied (but unrelated to the model) parameters to the controller? - php

This issue came up while maintaining an application written in CakePHP 1.3. Please bear in mind that while I can and do read the docs, my experience with Cake is quite limited.
The application has a Widget model and a WidgetController. When editing a Widget, one option the user has is to massively import data into the Widget in one of three modes: add to, remove from, or replace the widget data with what is imported.
The current implementation is a total mess (there is an "edit" action which performs all the 10 or so different mutation functions a Widget supports; it decides what to do exactly by sniffing the parameters from the submitted form), so I broke the "massive stuff" into a new action:
function batch($id)
{
// massively apply data to Widget $id; either add, remove or replace
}
This action is triggered by a form in the "edit" view:
// Only relevant elements shown
echo $form->create('Widget', array('enctype'=>'multipart/form-data', 'action' => 'batch')); ?>
echo $form->input('id');
echo $form->input('action', array('type'=>'select',
'options'=>array(
'append'=>__('Append', true),
'replace'=>__('Replace existing', true),
'delete'=>__('Delete specified', true)
));
echo $form->end(); ?>
As it stands, this form bundles the action parameter into the Widget array and the only way I can get hold of that from the controller is with $this->data['Widget']['action'].
This is ugly and semantically wrong, so I hope there's a better way to do it.
Ideally I 'd want to submit to the URL /widget/batch/X/append, but that's not possible because the append part is not fixed. So I 'd settle with any of these:
somehow pass the action as a controller parameter like $id
somehow pass the action as a named parameter, allowing $this->params['named']['action']
some other way that does not require installing a custom route and does not require JavaScript
Can Cake 1.3 do this?

No
no you cannot route a request to a different action based on a post parameter, nor with php alone change the action of a form. This is likely to be the same with any framework.
Ideally I'd want to submit to the URL /widget/batch/X/append, but that's not possible because the append part is not fixed.
The only way to do that is with javascript - of course it's not hard to write some appropriate js to do that.
However
If you don't want to use javascript to manipulate the form action when the user changes their selection, or just want the simplest solution - you can use setAction to internally redirect the request to more appropriate, dedicated, controller actions:
function batch() {
if (!empty($this->data['Widget']['action'])) {
$this->setAction($this->data['Widget']['action']);
}
}
function append() {
if ($this->data) {
if ($this->Widget->append($this->data)) {
$this->Session->setFlash('Widget appended successfully');
} else {
$this->Session->setFlash('Error: Unable to append widget');
}
}
}
In this way you can likely refactor the existing code into more manageable and logical methods.

Related

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.

insert/delete query design of website

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.

PHP MVC design - multiple actions to same url/controller

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

Form as HMVC widget

Is this the right way to create a form widget in FuelPHP?
class Controller_Widget extends Controller
{
public function action_show()
{
if (Request::is_hmvc())
{
// show form widget
}
else
{
// process form
}
}
}
The form action calls the same function to process, but where will it redirect to after? how will it show validation errors?
Note: The widget should not be accessible through the URL; the form should not display itself if accessed directly through the URL.
EDIT:
Found a similar problem in CodeIgniter HMVC and dynamic widgets but this is from 3 years ago. Maybe the FuelPHP guys have found a better way to do this.
This seems like a weird method, a method that is called show but handles both showing and manipulating data? A method called "show" (or get, fetch, read, etc) shouldn't do any editing, it's name expressly seems to imply that it's a read-only operation.
But also how it proceeds seems off. Its read-operation is HMVC only but its manipulation operation is non-HMVC only? That's really a wrong way to determine what the method should do, whether or not it is HMVC should have no baring on what it does.
In your case I'd split this up into 2 methods: one for retrieval (show()) and another for manipulation (edit() for example). Whether you want to make these HMVC only is up to you. There's more ways than one to solve that, I'd go with:
if ( ! Request::is_hmvc())
{
throw new Exception('Only HMVC access allowed.');
}
Or make it impossible to route to the method by rerouting it in your routes.php config file and then using the HMVC routing overwrite as discussed here: https://stackoverflow.com/a/9957367/727225

CakePHP View including other views

I have a CakePHP application that in some moment will show a view with product media (pictures or videos) I want to know if, there is someway to include another view that threats the video or threats the pictures, depending on a flag. I want to use those "small views" to several other purposes, so It should be "like" a cake component, for reutilization.
What you guys suggest to use to be in Cake conventions (and not using a raw include('') command)
In the interest of having the information here in case someone stumbles upon this, it is important to note that the solution varies depending on the CakePHP version.
For CakePHP 1.1
$this->renderElement('display', array('flag' => 'value'));
in your view, and then in /app/views/elements/ you can make a file called display.thtml, where $flag will have the value of whatever you pass to it.
For CakePHP 1.2
$this->element('display', array('flag' => 'value'));
in your view, and then in /app/views/elements/ you can make a file called display.ctp, where $flag will have the value of whatever you pass to it.
In both versions the element will have access to all the data the view has access to + any values you pass to it. Furthermore, as someone pointed out, requestAction() is also an option, but it can take a heavy toll in performance if done without using cache, since it has to go through all the steps a normal action would.
In your controller (in this example the posts controller).
function something() {
return $this->Post->find('all');
}
In your elements directory (app/views/element) create a file called posts.ctp.
In posts.ctp:
$posts = $this->requestAction('posts/something');
foreach($posts as $post):
echo $post['Post']['title'];
endforeach;
Then in your view:
<?php echo $this->element('posts'); ?>
This is mostly taken from the CakePHP book here:
Creating Reusable Elements with requestAction
I do believe that using requestAction is quite expensive, so you will want to look into caching.
Simply use:
<?php include('/<other_view>.ctp'); ?>
in the .ctp your action ends up in.
For example, build an archived function
function archived() {
// do some stuff
// you can even hook the index() function
$myscope = array("archived = 1");
$this->index($myscope);
// coming back, so the archived view will be launched
$this->set("is_archived", true); // e.g. use this in your index.ctp for customization
}
Possibly adjust your index action:
function index($scope = array()) {
// ...
$this->set(items, $this->paginate($scope));
}
Your archive.ctp will be:
<?php include('/index.ctp'); ?>
Ideal reuse of code of controller actions and views.
For CakePHP 2.x
New for Cake 2.x is the abilty to extend a given view. So while elements are great for having little bits of reusable code, extending a view allows you to reuse whole views.
See the manual for more/better information
http://book.cakephp.org/2.0/en/views.html#extending-views
Elements work if you want them to have access to the same data that the calling view has access to.
If you want your embedded view to have access to its own set of data, you might want to use something like requestAction(). This allows you to embed a full-fledged view that would otherwise be stand-alone.
I want to use those "small views" to
several other purposes, so It should
be "like" a cake component, for
reutilization.
This is done with "Helpers", as described here. But I'm not sure that's really what you want. The "Elements" suggestion seems correct too. It heavily depends of what you're trying to accomplish. My two cents...
In CakePHP 3.x you can simple use:
$this->render('view')
This will render the view from the same directory as parent view.

Categories