I'm looking to send the user to another page via a controller method. The other page expects POST data.
Normally, the page is accessed with a postLink(). Is there a way to use this in the controller, perhaps with redirect()?
A little bit old but still no answer accepted so...
The answer is no, and yes.
No, there is no direct method since you cannot pass POSTed data using redirect() function.
You could use requestAction(), since you can pass data as posted (see requestAction() here for version cakePHP>=2.0).
In that case you pass an url and then an array having the key data with the posted data, something like
$this->requestAction($url, array('data' =>$this->data));or if you prefer$this->requestAction($url, array('data' =>$this->request->data));
The problem with requestAction() is that the result is environmentally as if you were generating the page of the requested action in the current controller, not in the target, resulting in not very satisfactory effects (at least not usually for me with components behaving not very nicely), so still, no.
...but Yes, you can do something very similar using the Session component.
This is how I usually do it. The flow would be something like this:
View A=>through postLink() to Action in A controller=>=>A controller request->data to Session variable=>=>action in B controller through redirect()=>=>set B controller request->data from Session variable=>=>process data in B controller action=> View B
So, in your A controller, let's say in the sentToNewPage() action you would have something like
//Action in A controller
public function sentToNewPage()
{
$this->Session->write('previousPageInfo', $this->request->data);
$url = array('plugin' => 'your_plugin', 'controller'=>'B',
'action'=>'processFromPreviousPage');
$this->redirect($url);
}
and in B controller:
//Action in B controller
public function beforeFilter()
{//not completelly necessary but handy. You can recover directly the data
//from session in the action
if($this->Session->check('previousPageInfo'))
{$this->data = $this->Session->read('previousPageInfo')};
parent::beforeFilter();
}
public function processFromPreviousPage()
{
//do what ever you want to do. Data will be in $this->data or
// if you like it better in $this->request->data
$this->processUserData($this->request->data);
//...
}
Best solution would be use javascript to redirect.
But if you want more cake I give you some tools
CakeAPI: requestAction - it allow to execute controller method of desire with parameters, if you pass 'return', it will return full view output for that action.
//very useful in views
$result = $this->requestAction('Controller/method',
array('return','user_id'=>$userId)
);
parameter will be accessible in controller via request param
$this->request->params['user_id']
Long and short is that it's not easy to emulate an HTML form with POST data and a redirect, you kind of need to set a bunch of hidden variables containing the data and automatically post the form to your destination via Javascript.
What I would do is take the processing functionality out of the function that requires POST variables, and make it generic so that you can call it from both of your functions.
Consider this rough example:
public function myPostDataAction() {
$name = $_POST['name'];
$age = $_POST['age'];
// do stuff
echo $name . ', ' . $age;
}
Let's say that is the action you are trying to post data to in this scenario, but you can't because you can't emulate those $_POST variables over a redirect without the scenario mentioned at the top here. You can do this:
public function myPostDataAction() {
$name = $_POST['name'];
$age = $_POST['age'];
// call common function
echo $this->getMyResults($name, $age);
}
// accessible from inside the controller only
private function getMyResults($name, $age) {
return $name . ', ' . $age;
}
Now you can also use that getMyResults() functionality by passing regular old variables into it:
public function myProblemFunction() {
$name = 'John';
$age = 15;
echo $this->getMyResults($name, $age);
}
Now, obviously you won't be outputting anything like that straight from the controller action, you'll be setting it to your views etc, but that's an example of how you can centralize functionality to be used in multiple locations.
For the disclaimer, this kind of thing is exactly what models are for, and you should definitely consider putting this kind of function into a model instead of a controller - it depends on your specific application.
With cakephp 2.x Use this:
$this->autoRender = false;
$request = new CakeRequest(Router::url(array('controller' => 'mycontroller','action' => 'my_action')));
$request->data('dataIndex','value');
$response = new CakeResponse();
$d = new Dispatcher();
$d->dispatch(
$request,
$response
);
but it will not redirect but dispatch to a different controller/action so if you went on /controller/oldaction it will stays the current url (no HTTP redirection is done).
You could still change the url with javascript
see: Change the URL in the browser without loading the new page using JavaScript
Related
I'm very new to Laravel and I was given a Laravel project, where I need to add some new features. The person, who has previously worked on that project hadn't left even a single comment in the code and now I must make my own scenarios about the features.
I have a controller, defined with some functions (dashboard, show_project, save_project etc.) and in one of my function, I need to use the result of calling other function.
In the concrete example, the call is made from "http://127.0.0.1:8000/username/project_slug" - there is a button "Save" and post function, called on onClick event. The function, whose output I need is normally called on "http://127.0.0.1:8000/username/project_slug/svg", which returns a view.
For better understanding, there's an example of the flow:
The user wants to save his/her project (an UML diagram) but in order to have a thumbnail, a function which generates a view (SVG format) will be called and the idea is, to take the HTML content of the page, which is on "http://127.0.0.1:8000/username/project_slug/svg" and to pass it to another API in order an image to be generated.
So far, I tried with cURL, file_get_contents, file_get_html, render methods but when I return the output, the server just keeps waiting and shows no error messages.
//The both functions are in ProjectController.php
/**
* A function, for saving the json file, where the whole of the diagram
* components are described. From the frontend we receive the project_id and
* the project_data(the json content).
*/
public function save_project(Request $request) {
$input = $request->only(['project_id', 'project_data']);
/*
Here we need to call the other function, to render the HTML content
and to pass it to the other API. Then we save the result with the
other information.
*/
/*
What I've tried?
$new_link = 'http://' . $_SERVER['HTTP_HOST'] . "/$username"
."/$project_slug" . "/svg";
$contents = file_get_contents($new_link);
return $contents;
*/
//In the same way with cURL.
$project = Project::where('user_id',session('userid'))
->where('id',$input['project_id'])->first();
$project->project_data = json_encode($input['project_data']);
if($project->save()) {
return ["status"=>"saved"];
}
else {
return ["status"=>"error"];
}
}
/**
* A function, which takes the the Json content (project_data) from the
* database and passes it to the view, where the Json is transformed in HTML
* tags.
*/
public function generate_svg(Request $request,$username,$project_slug) {
if(session('username')!=$username) {
return redirect("/");
}
$userid = session('userid');
$project = Project::where([
'user_id' => $userid,
'slug' => $project_slug,
])->first();
if(!is_null($project)) {
return view('svg',compact('project'));
}
}
I've read about some possible ways, including Guzzle request but maybe I haven't understood correctly the idea:
If I need to make a Guzzle request from my controller to the other function inside my controller, do I need an API configuration?
What I mean? Example:
Before saving the project, the user is on this URL address "http://127.0.0.1:8000/hristo/16test". Inside the controller, I have in session variables the token, the username(hristo) and i can get the project_name(16test) from the URL but after passing this URL to the generate_svg function, there is no indication of error or success.
So I'm missing some kind of token information?
If you just need the response of the other function you can just use
$response = $this->generate_svg($request, $username, $project_slug);
If you'll need to use this function from a different controller you can use this
app('App\Http\Controllers\UsernameController')->generate_svg($request, $username, $project_slug);
In my controller i used this way. i want to pass a variable data to my index function of the controller through redirect
$in=1;
redirect(base_url()."home/index/".$in);
and my index function is
function index($in)
{
if($in==1)
{
}
}
But I'm getting some errors like undefined variables.
How can i solve this?
Use session to pass data while redirecting. There are a special method in CodeIgniter to do it called "set_flashdata"
$this->session->set_flashdata('in',1);
redirect("home/index");
Now you may get in at index controller like
function index()
{
$in = $this->session->flashdata('in');
if($in==1)
{
}
}
Remember this data will available only for redirect and lost on next page request. If you need stable data then you can use URL with parameter & GET $this->input->get('param1')
So in the controller you can have in one function :
$in=1;
redirect(base_url()."home/index/".$in);
And in the target function you can access the $in value like this :
$in = $this->uri->segment(3);
if(!is_numeric($in))
{
redirect();
}else{
if($in == 1){
}
}
I put segment(3) because on your example $in is after 2 dashes. But if you have for example this link structure : www.mydomain.com/subdomain/home/index/$in you'll have to use segment(4).
Hope that helps.
Use session to pass data while redirecting.There are two steps
Step 1 (Post Function):
$id = $_POST['id'];
$this->session->set_flashdata('data_name', $id);
redirect('login/form', 'refresh');
Step2 (Redirect Function):
$id_value = $this->session->flashdata('data_name');
If you want to complicate things, here's how:
On your routes.php file under application/config/routes.php, insert the code:
$route['home/index/(:any)'] = 'My_Controller/index/$1';
Then on your controller [My_Controller], do:
function index($in){
if($in==1)
{
...
}
}
Finally, pass any value with redirect:
$in=1;
redirect(base_url()."home/index/".$in);
Keep up the good work!
I appreciate that this is Codeigniter 3 question, but now in 2021 we have Codeigniter 4 and so I hope this will help anyone wondering the same.
CI4 has a new redirect function (which works differently to CI3 and so is not a like for like re-use) but actually comes with the withInput() function which does exactly what is needed.
So to redirect to any URL (non named-routed) you would use:
return redirect()->to($to)->withInput();
In your controller - I emphasise because it cannot be called from libraries or other places.
In the function where you are expecting old data you can helpfully use the new old() function. So if you had a key in your original post of FooBar then you could call old('FooBar'). old() is useful because it also escapes data by default.
If however, like me, you want to see the whole post then old() isn't helpful as the key is required. In that instance (and a bit of a cheat) you can do this instead:
print'<pre>';print_r($_SESSION['_ci_old_input']['post']);print'</pre>';
CI4 uses the same flash data methods behind the scenes that were given in the above answers and so we can just pull out the relevant session data.
To then escape the data simply wrap it in the new esc() function.
More info would be very helpful, as this should be working.
Things you can check:
Is your controller named home.php? Going to redirect(base_url()."home"); shows your home page?
Make your index function public.
public function index($in) {
....
}
I am experimenting with using the MVC pattern to set local vars in some code ie
$action=basename(__FILE__, '.php'); // load action from filename for consistancy (index for this case)
$controller = new seoController($action . '-seo'); // register controller with page action and parameter
$controller->invoke(); // invoke controller for processing
$page_title = "<insert page title here>";
$page_desc = "<insert page desc here>";
$page_keys = "<insert page keywords here>";
Of course the controller calls the model and does all the backend stuff parsing the input, getting the data and then returning.
What I would like is a clean way to set the local $page_title etc vars from the seoModel that is instantiated in setController without using the $_SESSION or any other hacky kind of way.
Is it ok from a design POV to put methods in the controller to get the info? ie
$page_title = seoController->getPageTitle();
My controllers as of now are not being used in this type of way as all they do is connect my models to the views.
I hope I'm being clear enough with my explanation.
Is it ok from a design POV to put methods in the controller to get the info?
Yes, thats what Controller is meant for.
What I would like is a clean way to set the local $page_title etc vars from the seoModel that is instantiated in setController without using the $_SESSION or any other hacky kind of way.
To avoid using $_SESSION that seems to be a bit overkill for this particular case you can set seoController attributes, for example,
Class seoController
{
$public $page_tile = '';
public method getPageTitle()
{
$model = new seoModel();
$page_title = $model->get_page_title();
$this->page_tile = $page_title;
//you could also return the page title here, skipping that
}
}
And access them from the caller
$controller = new seoController;
$controller->getPageTitle();
$page_title = $controller->page_title;
You would normally have things like meta tags stored with the model it’s describing. So if you’re loading say, a product from a model, then that model may also return the meta tags for that product:
public function show($productId)
{
$product = $this->productModel->findById($productId);
// Meta title may be available at $product->meta_title
return new ViewModel(array(
'product' => $product,
));
}
Your controller action would then return the data needed to be displayed in a view, which could be a HTML template, JSON, XML etc.
I have built a simple Notification system in my Cake app that I want to have a function that will create a new notification when I call a certain method. Because this is not something the user would actually access directly and is only database logic I have put it in the Notification model like so:
class Notification extends AppModel
{
public $name = 'Notification';
public function createNotification($userId, $content, $url)
{
$this->create();
$this->request->data['Notification']['user_id'] = $userId;
$this->request->data['Notification']['content'] = $content;
$this->request->data['Notification']['url'] = $url;
$result = $this->save($this->request->data);
if ($result)
{
$this->saveField('datetime', date('Y-m-d H:i:s'));
$this->saveField('status', 0);
}
}
}
And then whenever I want to create a notification within my app I just do:
$this->Notification->createNotification($userId,'Test','Test');
However this doesn't work! The controller is talking to the model fine, but it doesn't create the row in the database... I'm not sure why... but it would seem I'm doing this wrong by just doing all the code in the model and then calling it across the app.
Edit: Based on answers and comments below, I have tried the following the code to create a protected method in my notifications controller:
protected function _createNotification($userId, $content, $url)
{
$this->Notification->create();
$this->request->data['Notification']['user_id'] = $userId;
$this->request->data['Notification']['content'] = $content;
$this->request->data['Notification']['url'] = $url;
$result = $this->save($this->request->data);
if ($result)
{
$this->saveField('datetime', date('Y-m-d H:i:s'));
$this->saveField('status', 0);
}
}
Now the thing that is stumping me still (apologies if this is quite simple to others, but I have not used protected methods in CakePHP before) is how do I then call this from another controller? So for example If have a method in my PostsController and want to create a notification on successful save, how would I do this?
I thought about in my PostsController add method:
if($this->save($this->request-data){
$this->Notification->_createNotification($userId,'Test','Test');
}
But being protected I wouldn't be able to access the method from outside of the NotificationsController. Also I'm using the same syntax as if I was calling a function from a model so again it doesn't feel right.
Hopefully someone can help me out and get me back on track as this is a new area to me.
the controller should pass all data to the model
$this->createNotification($this->request->data);
the model then can use the data:
public function createNotification(array $data) {
$key = $data[$this->alias]['key'];
$data[...] = ...;
$this->create();
return $this->save($data);
}
you never ever try to access the controller (and/or its request object) from within a model.
you can also invoke the method from other models, of course:
public function otherModelsMethod() {
$this->Notification = ClassRegistry::init('Notification');
$data = array(
'Notification' => array(...)
);
$this->Notification->createNotification($data);
}
and you can make your methods verbose, but that usually makes it harder to read/understand/maintain with more and more arguments:
public function createNotification($userId, $content, $url) {
$data = array();
// assign the vars to $data
$data['user_id'] = $userId;
...
$this->create();
return $this->save($data);
}
so this is often not the cake way..
Methods in a model are not "publicly accessible" by definition. A user cannot call or invoke a method in a model. A user can only cause a controller action to be initiated, never anything in the model. If you don't call your model method from any controller, it's never going to be invoked. So forget about the "non-public" part of the question.
Your problem is that you're working in the model as if you were in a controller. There is no request object in a model. You just pass a data array into the model method and save that array. No need for $this->request. Just make a regular array(), put the data that was passed by the controller in there and save it.
The whole approach is totally wrong in the MVC context IMO and screams for the use of the CakePHP event system. Because what you want is in fact trigger some kind of event. Read http://book.cakephp.org/2.0/en/core-libraries/events.html
Trigger an Event and attach a global event listener that will listen for this kind of events and execute whatever it should do (save something to db) when an event happens. It's clean, flexible and extendible.
If you did a proper MVC stack for your app most, if not all, events aka notifications should be fired from within a model like when a post was saved successfully for example.
This is what I have ended up doing. While it certainly isn't glamorous. It works for what I want it to do and is a nice quick win as the notifications are only used in a few methods so I'm not creating a large amount of code that needs improving in the future.
First to create a notification I do the following:
$notificationContent = '<strong>'.$user['User']['username'].'</strong> has requested to be friends with you.';
$notificationUrl = Router::url(array('controller'=>'friends','action'=>'requests'));
$this->Notification->createNotification($friendId,$notificationContent,$notificationUrl);
Here I pass the content I want and the URL where the user can do something, in this case see the friend request they have been notified about. The url can be null if it's an information only notification.
The createNotification function is in the model only and looks like:
public function createNotification($userId, $content, $url = null)
{
$this->saveField('user_id',$userId);
$this->saveField('content',$content);
$this->saveField('url',$url);
$this->saveField('datetime', date('Y-m-d H:i:s'));
$this->saveField('status', 0);
}
This creates a new record in the table with the passed content, sets its status to 0 (which means unread) and the date it was created. The notification is then set as read when a user visits the notifications page.
Again this is most probably not an ideal solution to the problem outlined in this question... but it works and is easy to work with And may prove useful to others who are learning CakePHP who want to run functions from models when building prototype apps.
Remember nothing to stop you improving things in the future!
First of all, you can improve your last solution to do one save() (instead of 5) the following way:
public function createNotification($userId, $content, $url = null){
$data = array(
'user_id' => $userId,
'content' => $content,
'url' => $url,
'datetime' => date('Y-m-d H:i:s'),
'status' => 0
);
$this->create();
$this->save($data);
}
When I began programming CakePHP(1.3) more than a year ago I also had this problem.
(I wanted to use a function of a controller in any other controller.)
Because I didn't know/researched where to place code like this I've done it wrong for over a year in a very big project. Because the project is really really big I decided to leave it that way. This is what i do:
I add a function (without a view, underscored) to the app_controller.php:
class AppController extends Controller {
//........begin of controller..... skipped here
function _doSomething(){
//don't forget to load the used model
$this->loadModel('Notification');
//do ur magic (save or delete or find ;) )
$tadaaa = $this->Notification->find('first');
//return something
return $tadaaa;
}
}
This way you can access the function from your Notification controller and your Posts controller with:
$this->_doSomething();
I use this kind of functions to do things that have nothing to do with data submittance or reading, so i decided to keep them in the app_controller. In my project these functions are used to submit e-mails to users for example.. or post user actions to facebook from different controllers.
Hope I could make someone happy with this ;) but if you're planning to make a lot of these functions, it would be much better to place them in the model!
Hi out there in Stackland! Here's my problem:
I want to use my Zend controller to load an array from a database, and then pass it to javascript. I've decided the best way to do this is to use ajax to ask the controller for it's array, encode it in json, and then pass it down. However, I don't know how to pass the variable I loaded in my first action to the action that will pass it down when it gets called via ajax.
The original action which produces the view
public function indexAction()
{
$storeid = $this->getStoreId();
if(!$storeid)
{
$this->_forward('notfound');
return;
}
$store = $this->_helper->loadModel('stores');
$store->getByPrimary($storeid);
}
The action that will be called via ajax
public function getdataAction()
{
$this->_helper->Layout->disableLayout(); // Will not load the layout
$this->_helper->viewRenderer->setNoRender(); //Will not render view
$jsonResponse = json_encode($store);
$this->getResponse()->setHeader('Content-Type', 'application/json')
->setBody($jsonResponse);
}
What I want is to pass $store in indexAction to getdataAction so it can send store as the jsonResponse. Note, these are called at two different times.
Things I have tried that haven't worked:
setting $this->getRequest()->setParam('store', $store) in indexAction, and then using $this->getRequest()->getParam('store'), in getdataAction. I presume this hasn't worked because they're different http requests, so attaching a new param is useless.
using protected $_store in the controller itself, and then saving to it with indexAction, and using it in getdataAction. I'm not really sure why this isn't working.
Is there a good way to pass a variable in this manner? Is there a way to pass a variable between different controllers?(I assume the answer to one is the answer to the other). Could I store it in a controller helper? Do I have to use a session, which I know would work but seems unnecessary? Is there a better way to pass variables to javascript? Am I asking too many questions? Any help would be outstanding. Thanks.
Maybe I'm reading the question wrong, but you should be able to just move $store into the constructor:
public function __construct() {
$store = $this->_helper->loadModel('stores');
$store->getByPrimary($storeid);
}
and have it accessible in all *Action methods. Using sessions seems out of whack for this.
(disclaimer: I'm pretty new to ZF, so I'm interested in other answers found here, and have not tested the below!)
In your view, where you put the ajax call, you will probably address it like:
(See ZF Documentation)
<?= $this->ajaxLink("Example 2",
"/YourController/getdata",
array('update' => '#content',
'class' => 'someLink'),
array('store' => $this->store)); ?>
Notice that in your indexAction, you store the store via:
$this->view->store = $storeid;
Of course, you should note that a web-user could modify the store parameter as it is passed through via an URL.
It would be better architecture to simply add a method to your IndexController, a helper, or somewhere, that returns an instance of Store. Use that method within your indexAction, and your getdataAction (would be more meaningful to call it ajaxAction). Also, you're forgetting to call sendResponse() (remember, you disabled autoRender):
private function indexAction()
{
$this->getStore();
//blah blah
}
private function getStore()
{
$storeid = $this->getStoreId();
if(!$storeid)
{
$this->_forward('notfound');
return;
}
$store = $this->_helper->loadModel('stores');
$store->getByPrimary($storeid);
return $store;
}
public function ajaxAction()
{
$this->_helper->Layout->disableLayout(); // Will not load the layout
$this->_helper->viewRenderer->setNoRender(); //Will not render view
$jsonResponse = json_encode($this->getStore());
$this->getResponse()->setHeader('Content-Type', 'application/json')
->setBody($jsonResponse)
->sendResponse();
}
The manual says:
To send the response output, including
headers, use sendResponse().
http://framework.zend.com/manual/en/zend.controller.response.html
All right, for those of you who want the answer to this too, I just sucked it up and used session. I put a Zend_Session->start() in the bootstrap. I then created a plugin to add a private variable $session to each controller. Then I set $this->session to Zend_Session_Namespace. To pass something, I pass it through session, so I use $this->session->store = $store. I can then pick it up elsewhere with $this->session->store. Thanks to those who tried to help!
Just a quick addition to the comments. To output an array as JSON from within a controller, use:
$array = array('hi' => array('Hello' => 'World');
$this->_helper->json($array);
This sends the response and sets the specific headers for a JSON response