Apologies if I'm using the wrong terminology to describe what I'm trying to do...
I have a model/controller called Report which users can view like so:
example.com/reports/view/123
Each report hasAndBelongsToMany File objects. I need to make those files accessible like so:
example.com/reports/view/123/file/456
Or
example.com/reports/view/123/456
^ ^
| |
report file
I'm intentionally NOT creating a separate action for files (example.com/files/view...) because access to the file is relative to the report.
What is the correct way to do this in CakePHP?
My first guess is to add logic inside of ReportsController::view that checks for the existence of the second parameter (file) and manually render() a different view (for the file) conditionally. But I'm not sure if this is "the CakePHP way".
You are in the right path, modify your action to accept an optional parameter.
public function view($file = null) {
$somethingElse = null;
if (isset($file)) {
//your logic
$somethingElse = $this->Foo->bar();
}
$this->set(compact('somethingElse'));
}
Regarding to the view, I don't know your requirements, but I think you don't need to create a different view, you can either put a conditional in your view to show something, or (my favourite approach) create an element that will be displayed only if $somethingElse contains something. That is:
//View code
if (!empty($somethingElse)) {
echo $this->element('yourAwesomeElement', compact('somethingElse'))
}
Then in yourAwesomeElement
foreach ($somethingElse as $something) {
echo $something;
}
The good thing is that your element will be reusable for future views that could need this.
Related
I'm trying to achieve something so basic in my cakephp-app, that I'm quite surprised I didn't easily find a solution to it...
What I just want to do is to set available links for my app's main navigation depending on the user being logged in or not and if he is, depending on his role (which is stored in the users-table).
So basically a function like this:
if(!$this->request->is('ajax')) {
if(_user_is_not_logged_in_) {
$availableNavItems = array('login','help');
}
else {
if($this->Auth->User('role') == 'user') {
$availableNavItems = array('something','something else','whatever','help','logout');
}
elseif($this->Auth->User('role') == 'admin') {
$availableNavItems = array('something','something else','whatever','admin-tool','user management','help','logout');
}
}
// set available pages for layout
$this->set('availableNavItems',$availableNavItems);
}
In my layout of course I would create a navbar with links to those available pages.
The only question I have - where would I put code like the above? Is there any callback-function I could put in AppController which cakephp calls on every request?
And, what would be a good way to check what I wrote as pseudo-code "_user_is_not_logged_in_" above?
Thanks in advance for any help!
if(_user_is_not_logged_in_) {
could be written as
if(!$this->Auth->user('id')){
And you could put the function in your beforeRender method of your AppController, which executes on every request, right before the view is rendered.
Also of note is the beforeFilter method, which gets called early, before the controller logic executes. You shouldn't need it in this case, but it's worth knowing about.
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'm building a CMS based on CodeIgniter. It stores "views" and its data in a database and gathers the proper one when needed.
As you might have guessed - I can't generate a physical controller and matching view for each pages.
I've figured that routes would come in quite handy, since I'd prefer not to have to use a controller that's visible in the URL. Poorly explained: I'm looking for a way to reassign all requests that doesn't end up on a physically existing controller to a custom one - without it appearing in the URL. This controller would, of course, handle 404-errors and such.
Bad: .com/handler/actual-view/) Good: (.com/actual-view/) (no actual-view controller exists, or it'd be shown instead)
I've added a 404_override route which points to handler/. Now, I'm only looking for a way to find out the requested view (i.e in .com/actual-view actual-view is what I'm looking for).
I've tried
$route['404_override/(:any)'] = 'handler/$1';
and similar, which will remove the 404-override completely.
You would be better of extending the base Router or Controller.
By doing so you allow your application to be flexible and still conform to how CI works.
you need to define all of your valid routes in your route.php config file, and then at the last line,
$routes["(:any)"] = "specific controller path";
if I should give an example:
$route['u/(:any)/account'] = "user_profile/account/$1";
$route['u/(:any)/settings'] = "user_profile/settings/$1";
$route['u/(:any)/messages'] = "user_profile/messages/$1";
$route['u/(:any)'] = "user_profile/index/$1";
as seen here, I'm diverting all the urls to user profile, after the first three one couldn't catch it.
My solution, after some guidance from CodeIgniters awesome forum and StackOverflow's lovely members, became to route all 404 errors to my custom controller where I ensure that it's a real 404 (no views OR controllers). Later in the controller, I gather the rest of the info I need from my database URI string:
//Route
$route['404_override'] = 'start/handler';
//Controller
function handler($path = false) {
//Gather the URI from the URL-helper
$uri_string = uri_string();
//Ensure we only get the desired view and not its arguments
if(stripos($uri_string, "/") !== false) {
//Split and gather the first piece
$pieces = explode("/", $uri_string);
$desired_view = $pieces[0];
} else {
$desired_view = $uri_string;
}
//Check if there's any view under this alias
if($this->site->is_custom_view($desired_view)) {
//There is: ensure that the view has something to show
if(!$this->site->view_has_data($desired_view)) {
//No data to show, throw an error message
show_custom_error('no_view_data');
} else {
//Found the views data: show it
}
} else {
//No view to show, lets go with 404
show_custom_404();
}
}
I'm having an issue with preserving the value of a variable after an HMVC sub-request in Kohana 3.1.3.1 and am wondering how best to approach/fix it. I thought that additional requests in Kohana were isolated from each other, but it doesn't seem to be the case...
First of all, I've created a controller to extend the Controller_Template:
abstract class Controller_Website extends Controller_Template {
public $page_info;
public $allow_caching;
public function before()
{
// ... more unrelated code here ...
// Only do this if auto_render is TRUE (default)
if ($this->auto_render === TRUE AND $this->request->is_initial())
{
// Set our Page_Info to the values we just loaded from the database
$this->page_info = clone $this->navs[$slug];
}
// ... more unrelated code here ...
}
public function after()
{
// ... more unrelated code here ...
// For internal requests, let's just get everything except for the template
if (! $this->request->is_initial())
{
$this->response->body($this->template->main_view->render());
}
// Only if auto_render is still TRUE (Default)
if ($this->auto_render === TRUE AND $this->request->is_initial())
{
// ... more unrelated code here ...
// ... get stuff from the database to populate the template ...
// now render the response body
$this->response->body($this->template->render());
}
// ... more unrelated code here...
// including setting headers to disable/enable caching
}
}
And here's an example of what one of the controllers looks like:
class Controller_Events extends Controller_Website {
public function action_filtered()
{
// ... do some stuff ...
// and set some values
$this->page_info->page_title = 'Set in Events controller';
// and some more stuff including generating some output
}
}
Now I want one of my other controllers to be able to pull the output from the events controller, without the template. Controller_Website (above) takes care of excluding the template from the output, but consider this:
class Controller_Search extends Controller_Website {
public function action_index()
{
// ... do some stuff ...
// now let's include the result from our events controller
$this->template->main_view->events = Request::factory()
->controller('events')
->action('filtered')
->execute();
// and set some values
$this->page_info->page_title = 'Set in Search controller';
// and some more stuff including generating some output
}
}
So when my template calls echo $this->page_info->page_title; (remember, my template is only being included in the search controller's output and not the event controller's output), I'm expecting it to return "Set in Search controller" but instead it returns "Set in Events Controller"
The problem is that this action_filtered() method is very long and I've set up a couple routes that use this method to output several event pages (like filtering events by year, month, venue, city, etc.) so it doesn't make sense to duplicate this method in my search controller. Hence the need for an HMVC request. When the filtered action is called as a main/initial request, it makes sense to set values in $page_info but when it's called as a sub-request, I need to preserve the values set in the search controller, or whatever the initial controller is.
Of course, I could create an if statement in the events controller to only update these values if it's a main request, but that's less than ideal, obviously. There must be a way to make this sub-request run isolated from the initial request?
What am I doing wrong, or what's the best way to go about solving this?
Thanks in advance!
DM
Request::factory()
->controller('events')
->action('filtered')
->execute();
Its incorrect. Request::factory() call returns an initial Request instance (so it uses current URI value). You need a generated URI for your HMVC call:
Request::factory(Request::current()->uri(array(
'controller' => 'events',
'action' => 'filtered'
)))->execute();
PS. Sorry, its my mistake. Your code seems to be valid in 3.1. Anyway, try to change it with Request->uri() usage.
I found the problem/solution!
The problem is that in my Controller_Website I had this in my action_before():
// Create a new DM_Nav object to hold our page info
// Make this page info available to all views as well
$this->page_info = new DM_Nav;
View::bind_global('page_info', $this->page_info);
The problem is the bind_global – which is actually working as it's supposed to by letting you change the value of this variable after the fact... (A very neat feature indeed.)
The workaround/solution was to force the template to use only the original page_info by detecting if it was an initial/main request. So at the very end of the action_before() method of Controller_Website where it says:
// Only if auto_render is still TRUE (Default)
if ($this->auto_render === TRUE AND $this->request->is_initial())
{
I added this line to the end of that if statement:
$this->template->page_info = $this->page_info;
}
This line is redundant on initial/main requests, but it means that any additional sub-requests can still have access to their own page_info values without affecting the values used in the template. It appears, then, that if you assign a property to a view and then attempt to bind_global() the same property with new values, it doesn't overwrite it and uses the original values instead... (Which is why this solution works.) Interesting.
I am having some trouble trying to "capture" the rendered html of an elmenet in cake php.
Say I have an element named "message.ctp"
I would like to do something like the following:
A making a $.getJSON request to an action in a controller say jsonAction(). Within this action I will perform some DB updates and return a json string. I would like to store the html is a part of the json object. Doable?
function jsonAction() {
//Do DB update
if(db update was ok) {
$response = array("completed" => true, "html" => $this->render("message"));
} else {
$response = array("completed" => false);
}
echo json_encode($response);
}
What seems to be happening right now is that the render method echos the rendered value instead of returning it.
Anyway I can achieve this?
Thanks
Regards
Gabriel
Forget elements for the time being.
First of all you have to separate everything that includes outputting stuff from the controller (either it is HTML or JSON or whatever).
For every controller action you have to have a corresponding view. So, for controller action jsonAction you should have a view names json_action.ctp (at the corresponding folder, e.g. if jsonAction is at MessagesController create a folder named /view/messages/json_action.ctp).
Set your variable from controller, display it at view and you are done. Do not forget to $this->layout = 'empty' from controller so that you display only what you have at the view.
Generally you should redo the CakePHP tutorials and reread the book it order to understand better the MVC (Model-View-Controller) pattern and CakePHP structure.
Do you mean this?
$myRenderedHtml = $this->element('message');
^^^^^^^